<?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[Siro Dev's Blog]]></title><description><![CDATA[Siro Jack, Siro Dev, Flutter, NextJs, Laravel, Android, iOS, Jet Pack Compose]]></description><link>https://siro.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 05 Nov 2026 08:44:22 GMT</lastBuildDate><atom:link href="https://siro.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Optimizing Your Flutter App for the Web: A Practical Guide]]></title><description><![CDATA[Flutter Web has evolved considerably in recent years, becoming a viable option for building high-performance Progressive Web Apps (PWAs), dashboards, admin…]]></description><link>https://siro.hashnode.dev/optimizing-your-flutter-app-for-the-web-a-practical-guide</link><guid isPermaLink="true">https://siro.hashnode.dev/optimizing-your-flutter-app-for-the-web-a-practical-guide</guid><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Mon, 26 Oct 2026 06:22:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6152c793414f78089df39fdc/186c4080-ff51-4775-9da5-513c022f5114.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2>
<p>Flutter Web has evolved considerably in recent years, becoming a viable option for building high-performance Progressive Web Apps (PWAs), dashboards, admin panels, SaaS platforms, and enterprise applications. What was once dismissed by some developers as an impractical choice for web development is now powering real-world production applications. Its single-codebase approach, comprehensive widget ecosystem, and growing support for WebAssembly make it an increasingly attractive option for building sophisticated web products.</p>
<p>Despite these improvements, Flutter Web apps can still struggle with slow initial loading, large JavaScript bundles, and poor Core Web Vitals scores when performance optimization is overlooked. These challenges become more noticeable as applications grow in complexity. Features that make an application powerful—offline-first databases such as Drift and Floor, high-resolution images and videos, platform-specific integrations, real-time synchronization with backends like Supabase, and extensive screen collections—can also introduce significant performance bottlenecks if they aren't implemented carefully. An application that loads almost instantly on a high-end device may take several seconds to become interactive on a mid-range laptop with an unreliable internet connection.</p>
<p>In this guide, we'll examine practical strategies for optimizing Flutter Web performance, reducing loading times, and delivering a smoother user experience. Rather than focusing on a simple demo or counter application, we'll explore techniques suited to real-world projects with complex features, substantial data requirements, and growing codebases.</p>
<h3>Why Flutter Web Performance Matters</h3>
<p>Website speed directly affects:</p>
<ul>
<li><strong>User engagement</strong> — users abandon apps that feel sluggish</li>
<li><strong>Bounce rate</strong> — slower pages lose visitors before they ever interact</li>
<li><strong>SEO rankings</strong> — Core Web Vitals are a ranking signal</li>
<li><strong>Conversion rates</strong> — every extra second of load time costs revenue</li>
<li><strong>Accessibility</strong> — fast, responsive UIs are usable on low-end devices and poor networks</li>
<li><strong>Overall user satisfaction</strong> — performance <em>is</em> the experience</li>
</ul>
<p>Even a one-second improvement in page load time can significantly increase user retention.</p>
<h3>Common Performance Problems</h3>
<p>Typical Flutter Web applications encounter issues such as:</p>
<ul>
<li>Large initial bundles</li>
<li>Slow first paint</li>
<li>Excessive widget rebuilds</li>
<li>Large image assets</li>
<li>Un-optimized fonts</li>
<li>Heavy third-party packages</li>
<li>Blocking API calls</li>
</ul>
<p>Advanced apps add their own bottlenecks on top of these: offline-first databases that stall startup, animated media that leaks memory, sync engines that flood the UI with updates, and plugins that silently break under WebAssembly.</p>
<p>Fortunately, most of these problems can be resolved with proper optimization—and this guide walks through each of them.</p>
<hr />
<h2>1. Start with the Renderer: CanvasKit vs. WASM vs. Skwasm</h2>
<p>The renderer you choose sets the performance ceiling for everything else.</p>
<p><strong>CanvasKit</strong> compiles Skia to WebAssembly and delivers consistent rendering across browsers. It is excellent for graphics-intensive apps, but it carries a larger initial download and higher memory usage. The Flutter team acknowledges that CanvasKit’s inherent file size (around 1.5 MB) can be a drawback when initial load time is critical, because all widgets must wait for it to fully load before the app becomes interactive.</p>
<p><strong>WASM (dart2wasm)</strong> is now the recommended production default for Flutter web. It compiles Dart to WebAssembly, reduces app size compared to CanvasKit, and starts up faster than JavaScript. The caveat: WASM requires browsers with WasmGC support, so a CanvasKit fallback is still necessary for legacy browsers.</p>
<p><strong>Skwasm</strong> is an experimental WebAssembly renderer that often beats CanvasKit on startup time and frame performance, especially in multi-threaded mode. It is worth tracking if your app is rendering-bound.</p>
<p><strong>Practical rule:</strong> Build with <code>flutter build web --wasm</code> for modern browsers, and ship a CanvasKit fallback. Do not optimize for the lowest common denominator by default—your analytics will tell you how many users actually need the fallback.</p>
<hr />
<h2>2. Bundle Size Is the First Bottleneck: Split, Shake, and Lazy-Load</h2>
<p>A default Flutter web release build can easily ship a 2–5 MB <code>main.dart.js</code> plus a CanvasKit payload. For a feature-heavy app, this is the single biggest threat to time-to-interactive and Core Web Vitals. The progremmo guide lists large initial bundles, slow first paint, and heavy third-party packages as the most common performance problems in Flutter web apps.</p>
<h3>Deferred loading (code splitting)</h3>
<p>Dart’s <code>deferred as</code> keyword splits your app into on-demand JS chunks. The Flutter documentation explains that deferred imports let you split your code and load parts of it only when needed, reducing initial load time. Instead of bundling every screen into the initial download, only the home screen ships upfront; each route loads its own chunk when visited.</p>
<p>The impact is dramatic. In a real 12-screen Flutter web app, initial JS transfer dropped from <strong>~3.2 MB to ~780 KB (−76%)</strong>, and Time to First Contentful Paint dropped from <strong>~4.1 s to ~1.0 s on Slow 3G</strong>.</p>
<pre><code class="language-dart">// Before
import 'screens/dashboard_screen.dart';

// After
import 'screens/dashboard_screen.dart' deferred as dashboard;
</code></pre>
<p>Wrap the route builder with a lazy loader (or use <code>flutter_lazy_load_web</code> to avoid boilerplate):</p>
<pre><code class="language-dart">GoRoute(
  path: '/dashboard',
  builder: lazy(dashboard.loadLibrary, dashboard.DashboardScreen.new),
)
</code></pre>
<h3>Unawaited function calls</h3>
<p>A subtle but high-impact optimization from the Flutter team: to reduce the time taken to display the initial widget, <strong>do not</strong> <code>await</code> <strong>costly futures before calling</strong> <code>runApp</code>. Some futures can be left un-awaited so they update the UI after they complete, using Dart’s <code>unawaited</code> function to satisfy the linter.</p>
<pre><code class="language-dart">void main() {
  WidgetsFlutterBinding.ensureInitialized();
  unawaited(_warmUpAnalytics()); // do not block first frame
  runApp(const MyApp());
}
</code></pre>
<h3>Tree shaking and release mode</h3>
<p>Tree shaking complements deferred loading. In release builds, <code>dart2js</code> removes unreferenced code. The biggest win is often Material Icons—switching from the full icon set to only-used icons saves hundreds of kilobytes. The progremmo checklist recommends removing unused packages, avoiding unnecessary library imports, splitting large features, and tree-shaking icons before deployment.</p>
<hr />
<h2>3. Asset Management: Images, Video, and the Memory Trap</h2>
<p>Media is where advanced apps get into trouble on the web—especially because the renderer’s memory model differs from mobile. The progremmo guide warns that large image assets and un-optimized fonts are among the most common causes of slow first paint.</p>
<h3>Serve modern formats and size correctly</h3>
<p>Use <strong>WebP or AVIF</strong> instead of PNG/JPEG, and always request the size you actually render. The colinchflutter optimization guide specifically recommends WebP for better compression efficiency and suggests tools like <code>cwebp</code> or <code>imagemin</code> for pre-deployment optimization. Flutter’s <code>cacheWidth</code> parameter resizes decoded images to the width you specify, avoiding full-resolution decodes for small UI elements.</p>
<pre><code class="language-dart">Image.network(
  'https://cdn.example.com/hero_1920w.webp',
  width: 800,
  cacheWidth: 800, // decode at display size
)
</code></pre>
<h3>Use a dedicated image pipeline with caching</h3>
<p>For a media-heavy app, do not rely on <code>Image.network</code> alone. Packages like <strong>Piksel</strong> provide a three-tier cache (memory LRU, disk cache, raw download cache), scroll-aware prefetching, and support for SVG, AVIF, GIF, and video frames through one pipeline. On web, disk tiers persist in IndexedDB, which is critical for repeat visits.</p>
<h3>Pre-cache the first screen, not everything</h3>
<p>Pre-caching warms both the browser cache and Flutter’s in-memory cache, eliminating font swaps and blank images on first paint. Combine HTML <code>&lt;link rel="preload"&gt;</code> hints in <code>index.html</code> with <code>precacheImage</code> in Dart. Pre-cache only what the user will see in the next one or two screens—the logo, hero image, main font variants, and the first API request.</p>
<h3>The animated-image memory leak</h3>
<p>This is a web-specific trap that does not exist on mobile. On web, when an animated image (GIF or animated WebP) remains in the <code>ImageCache</code>, its <code>_ForwardingImageStreamCompleter</code> keeps a listener on the inner completer even after the widget is removed from the tree. The animation keeps decoding frames at its own frame rate, consuming CPU and growing the WASM heap.</p>
<p>In one production feed, 14 off-screen animations were still decoding at 8–11 fps each, costing about one CPU core in Firefox and 0.7 in Chrome while the app sat idle. The CanvasKit WASM heap grew to <strong>224 MB</strong>.</p>
<p><strong>Mitigation:</strong> call <code>imageCache.clear()</code> when navigating away from media-heavy screens, or use a package that manages completer lifecycle correctly.</p>
<hr />
<h2>4. Offline-First Databases on the Web: Drift, Floor, and Friends</h2>
<p>Offline-first is no longer just a mobile concern—users expect instant loads and reliable edits regardless of network state. But not all Flutter databases have equal web support.</p>
<table>
<thead>
<tr>
<th>Database</th>
<th>Web support</th>
<th>Best for</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Drift</strong></td>
<td><strong>Best overall web story</strong> via <code>sqlite3.wasm</code> + worker</td>
<td>Production CRUD, reporting, long-lived offline-first apps</td>
</tr>
<tr>
<td><strong>Floor</strong></td>
<td>Possible via <code>sqflite_common_ffi_web</code> (experimental)</td>
<td>Teams already invested in the Floor/Room-style API</td>
</tr>
<tr>
<td><strong>Hive</strong></td>
<td>Possible, but performance varies</td>
<td>Lightweight key-value caching, simple offline data</td>
</tr>
<tr>
<td><strong>Isar</strong></td>
<td>Depends on current platform support</td>
<td>Offline-first object graphs, fast reads</td>
</tr>
<tr>
<td><strong>sqflite</strong></td>
<td>Typically not for web (use <code>sqflite_common_ffi_web</code>)</td>
<td>Mobile-only SQL apps</td>
</tr>
</tbody></table>
<h3>Drift: the production-grade SQL choice</h3>
<p>Drift runs on the web by compiling SQLite to WebAssembly. You place a prebuilt <code>sqlite3.wasm</code> file and a <code>drift_worker.js</code> worker into your <code>web/</code> directory, then open the database with <code>WasmDatabase.open</code>. The worker shares the database across tabs, and the database persists in IndexedDB. Drift’s documentation notes that the web build requires <code>sqlite3.wasm</code>, COOP, and COEP headers to function correctly.</p>
<p>This is the most mature SQL option for web-capable offline-first apps that need joins, migrations, and reactive queries.</p>
<h3>Floor: the Room-inspired alternative</h3>
<p>Floor provides a neat SQLite abstraction inspired by Android’s Room persistence library, with automatic mapping between in-memory objects and database rows while still allowing full SQL control. It is a strong choice if your team prefers the Floor API or is porting an existing mobile codebase.</p>
<p>However, <strong>Floor does not officially list web among its supported platforms</strong>—its documented targets are iOS, Android, Linux, macOS, and Windows. Because Floor is built on top of sqflite, web support is technically possible by switching to the <code>sqflite_common_ffi_web</code> database factory, which implements sqflite on the web using <code>sqlite3</code> compiled to WebAssembly, with persistence in IndexedDB and SQLite running in a shared worker. This path is <strong>experimental</strong>: the <code>sqflite_common_ffi_web</code> package itself describes its status as “slow, not fully tested, bugs,” and notes limitations such as lack of <code>deleteDatabase</code> support on WASM.</p>
<p>If you choose Floor for web, budget time for this integration and test thoroughly across browsers. If web is a first-class target and you need SQL guarantees, Drift is the safer bet.</p>
<h3>The repository pattern keeps web and mobile logic aligned</h3>
<p>The core architectural principle is the <strong>Repository Pattern</strong>. Your UI talks to a repository, which orchestrates an <code>ApiClientService</code> (remote HTTP) and a <code>DatabaseService</code> (local persistence). The UI never knows whether data came from cache or server.</p>
<p>For reads, the <strong>Stream-based approach</strong> is the gold standard for responsive UIs: the repository immediately yields locally cached data (instant paint), while a network call runs in the background and updates the database; the stream then emits fresh data and the UI updates seamlessly.</p>
<h3>PWA service workers for shell caching</h3>
<p>Flutter web’s default service worker uses an <strong>offline-first</strong> strategy: it eagerly caches the application shell and lazily caches subsequent assets as they load. You can control this with <code>flutter build web --pwa-strategy=offline-first</code>, which pre-caches all assets for reliable offline startup.</p>
<p>A common production strategy is <strong>network-first for API calls, cache-first for static assets</strong>—so the shell loads instantly even on a flaky connection, while data stays fresh when the network is available. The progremmo guide emphasizes caching images, fonts, API responses, static assets, and JavaScript files through service workers to provide offline support and efficient asset management.</p>
<hr />
<h2>5. Syncing with Supabase: Connecting Your Local Database to the Cloud</h2>
<p>If your app uses Supabase as its backend, you need a deliberate sync strategy. Supabase provides authentication, Postgres, real-time subscriptions, and row-level security, but <strong>offline-first behavior is not automatic</strong>—you must pair it with a local database and a sync layer.</p>
<h3>The architecture: local DB + Supabase + sync engine</h3>
<p>The reliable pattern is:</p>
<ol>
<li><strong>Local database</strong> (Drift or Floor) holds the source of truth for the UI.</li>
<li><strong>Supabase</strong> acts as the remote source of truth and real-time event source.</li>
<li><strong>A sync engine</strong> reconciles the two: pull changes on startup and on connectivity regain, push queued local writes when online, and resolve conflicts deterministically.</li>
</ol>
<p>Several packages implement this pattern. <code>syncitron</code> transforms an online-only Supabase app into an offline-first platform by handling bidirectional synchronization, with the local store being SQLite, Hive, Drift, or Isar, and remote adapters connecting to Supabase. <code>dynos_sync</code> is a production-grade sync engine that works with Drift + Supabase and supports delta pulls and background isolate offloading for large datasets. PowerSync also offers a Flutter library that keeps an embedded database in sync with Supabase in the background.</p>
<h3>Key Supabase sync practices for web</h3>
<ul>
<li><strong>Queue writes offline.</strong> Every insert, update, and delete should be written locally first and appended to an offline write queue. When connectivity returns, flush the queue in order. <code>supabase_flutter_ultra</code>, for example, adds an offline write queue and delta sync on top of the standard Supabase APIs—but check its platform support carefully, because some versions depend on <code>sqflite</code> and may not support web.</li>
<li><strong>Use delta sync, not full table downloads.</strong> Pull only rows changed since the last sync timestamp or cursor. Full-table syncs on web are a fast path to memory pressure and slow startup.</li>
<li><strong>Handle conflicts explicitly.</strong> Decide your conflict resolution policy up front—last-write-wins, server-wins, or a custom merge—and encode it in the sync engine, not scattered across repositories.</li>
<li><strong>Leverage Supabase Realtime selectively.</strong> Realtime subscriptions are powerful, but on web they hold open WebSocket connections and consume memory. Subscribe only to tables that truly need live updates, and unsubscribe when the relevant screen is disposed.</li>
<li><strong>Keep auth tokens fresh without blocking.</strong> Supabase’s client handles token refresh, but do not <code>await</code> auth restoration before <code>runApp</code>. Restore the session asynchronously and let the UI render a loading or cached state immediately.</li>
</ul>
<h3>Web-specific Supabase caveat</h3>
<p>Supabase’s official Flutter SDK works on web, but many “offline-first wrapper” packages are mobile-first and may not list web support. Before adopting a sync package, verify its platform support on pub.dev and test the IndexedDB/WebAssembly path. If a wrapper is not web-ready, you can still use the core <code>supabase_flutter</code> package for remote calls and implement the sync loop yourself against Drift or Floor.</p>
<hr />
<h2>6. Responsive Design: Breakpoints, Not Guesswork</h2>
<p>A feature-heavy mobile app ported to web often looks like a stretched phone UI. Responsive design on Flutter web is not optional—it is a performance and usability requirement. The colinchflutter guide stresses using <code>LayoutBuilder</code> and media queries to dynamically adjust the UI based on available space, and optimizing <code>Flex</code> and <code>Expanded</code> widgets to ensure efficient space distribution and avoid unnecessary renderings.</p>
<p>Use <code>MediaQuery.sizeOf(context)</code> for whole-window decisions and <code>LayoutBuilder</code> for parent-constraint decisions (especially in multi-window and foldable contexts). Define explicit breakpoints for phone, tablet, and desktop, and switch navigation patterns (bottom nav → navigation rail → side drawer).</p>
<p><strong>Performance note:</strong> Responsive layouts that rebuild large widget subtrees on every resize event can cause jank. Debounce breakpoint changes, and use <code>const</code> widgets wherever possible. The progremmo guide explicitly recommends reducing widget rebuilds with const constructors, splitting large widgets, using selectors, and managing state efficiently.</p>
<hr />
<h2>7. Web-Specific Plugins and Native Interop</h2>
<p>Not every Flutter plugin supports web. Before committing to a plugin, check its platform support matrix. For plugins that lack web implementations, you have two paths:</p>
<ol>
<li><strong>Use a web-compatible alternative</strong> that exposes a unified API.</li>
<li><strong>Write a thin web implementation</strong> using <code>dart:js_interop</code> and <code>package:web</code>.</li>
</ol>
<p>The modern interop stack replaces the legacy <code>dart:html</code> library and is fully compatible with WASM builds. The Flutter team specifically notes that <code>dart:html</code> is not supported when building with WASM, whereas <code>package:web</code> works with both <code>dart2wasm</code> and <code>dart2js</code>.</p>
<pre><code class="language-dart">import 'dart:js_interop';
import 'package:web/web.dart';

final input = document.querySelector('#email') as HTMLInputElement;
input.value = 'user@example.com';
</code></pre>
<p><strong>Caution:</strong> Native features that rely on device APIs (file system, Bluetooth, biometrics) often have no direct web equivalent. Design your architecture so that these features degrade gracefully on web—hide unavailable UI, provide browser-based alternatives (File System Access API, Web Bluetooth), and never block the entire app on a missing plugin.</p>
<hr />
<h2>8. SEO and Discoverability: The Flutter Web Elephant</h2>
<p>Out of the box, Flutter web serves an app shell with minimal crawlable HTML. Search engines and social bots that do not execute JavaScript see a blank canvas.</p>
<p>For <strong>app-like routes</strong> behind authentication, this may be acceptable. For <strong>public-facing pages</strong>—marketing, documentation, product listings—it is a significant limitation.</p>
<h3>Practical SEO strategies</h3>
<ul>
<li><strong>Pre-render critical routes</strong> to static HTML. Tools like <code>flutter_prerender</code> load each route in headless Chrome and emit real <code>&lt;h1&gt;</code>, <code>&lt;p&gt;</code>, <code>&lt;a&gt;</code>, meta tags, and a sitemap.</li>
<li><strong>Inject meta tags dynamically per route</strong> using <code>dart:js_interop</code> to update <code>document.title</code> and meta description as the user navigates.</li>
<li><strong>Use a hybrid architecture</strong>: pre-render public pages as static HTML and mount the Flutter app for authenticated, app-like experiences. This gives you SEO where it matters without sacrificing the Flutter development model for the core product.</li>
</ul>
<hr />
<h2>9. Measure, Profile, and Iterate</h2>
<p>Optimization without measurement is guesswork. The colinchflutter guide highlights Flutter DevTools for profiling CPU usage, memory allocation, and network requests to identify bottlenecks. The progremmo guide adds Lighthouse, Chrome DevTools, and WebPageTest as essential tools for monitoring Core Web Vitals and validating improvements.</p>
<p>Two metrics to anchor on:</p>
<ul>
<li><strong>Lighthouse</strong>: Target <strong>LCP &lt; 2.5 s</strong>, <strong>FID &lt; 100 ms</strong>, and <strong>CLS &lt; 0.1</strong>.</li>
<li><strong>WASM heap profiling</strong>: Pay special attention to the WASM heap when using CanvasKit or Skwasm—memory leaks in image and canvas lifecycles are common on web.</li>
</ul>
<p>Build a performance budget into CI: if a pull request increases the initial bundle by more than a threshold, fail the build. The progremmo production checklist is a useful starting point: enable release mode, optimize images, compress assets, enable HTTP caching, lazy-load features, remove unused dependencies, use const widgets, optimize fonts, and test Lighthouse scores before every deploy.</p>
<hr />
<h2>Key Takeaways (Short and Actionable)</h2>
<ol>
<li><strong>Pick the right renderer.</strong> Use WASM for modern browsers; keep CanvasKit as a fallback.</li>
<li><strong>Split your bundle per route.</strong> Deferred loading cuts initial JS by 70%+ and first paint to about 1 second on 3G.</li>
<li><strong>Don’t block</strong> <code>runApp</code><strong>.</strong> Use <code>unawaited</code> for warm-up tasks so the first frame renders sooner.</li>
<li><strong>Optimize media.</strong> WebP/AVIF, display-size decoding, and an image cache lifecycle that doesn’t leak animations.</li>
<li><strong>Choose Drift for SQL-first offline web apps.</strong> Floor is viable via <code>sqflite_common_ffi_web</code>, but its web support is experimental—test carefully.</li>
<li><strong>Sync Supabase deliberately.</strong> Local DB + queue + delta sync + explicit conflict policy. Verify web support of any sync wrapper before adopting it.</li>
<li><strong>Design for breakpoints from day one.</strong> A stretched mobile UI is both a UX and performance failure.</li>
<li><strong>Measure with Lighthouse and DevTools.</strong> Treat performance as a CI-enforced feature, not an afterthought.</li>
</ol>
<hr />
<h2>Conclusion</h2>
<p>Optimizing a feature-heavy Flutter app for the web is not a single switch—it is a series of deliberate trade-offs. The good news is that every bottleneck discussed in this guide has a proven remedy:</p>
<ul>
<li><strong>Renderer choice</strong> sets the ceiling. Build with <code>--wasm</code> for modern browsers and keep CanvasKit as a fallback .</li>
<li><strong>Bundle size</strong> is the first thing users feel. Deferred loading turns a multi-megabyte initial download into per-route chunks, cutting time-to-interactive dramatically .</li>
<li><strong>Media</strong> demands a pipeline, not an <code>Image.network</code> call. WebP/AVIF, display-size decoding, and lifecycle-aware caching prevent the silent memory leaks that animated images cause on web.</li>
<li><strong>Offline-first databases</strong> work on web—Drift is the production-grade SQL choice via <code>sqlite3.wasm</code>, while Floor is viable through <code>sqflite_common_ffi_web</code> if you accept its experimental status .</li>
<li><strong>Supabase syncing</strong> requires a local-first architecture: write locally, queue offline mutations, delta-sync on reconnect, and resolve conflicts explicitly. Packages like <code>synclayer_supabase</code>, <code>supabase_flutter_ultra</code>, and PowerSync’s Supabase integration implement this pattern so you do not have to build it from scratch .</li>
<li><strong>Responsive design and performance measurement</strong> are not polish—they are requirements. Lighthouse and DevTools close the loop, and a CI performance budget keeps optimizations from regressing.</li>
</ul>
<p>The Flutter team’s own guidance reinforces this: measure rendering performance in profile/release mode, avoid unnecessary widget rebuilds, and treat deferred loading and tree shaking as first-class optimization tools . The Flutter Material team’s experience optimizing the Flutter Gallery app confirms that these techniques deliver measurable wins in production .</p>
<p>Flutter web can absolutely handle a feature-heavy, offline-first, media-rich application. The difference between a frustrating experience and a seamless one is not the framework—it is the discipline of optimizing the bottlenecks that advanced apps inevitably introduce.</p>
<hr />
<h3>References and Further Reading</h3>
<p><strong>Flutter Web Performance (Official)</strong></p>
<ul>
<li>Flutter — Performance best practices: <a href="https://docs.flutter.dev/perf/best-practices">https://docs.flutter.dev/perf/best-practices</a></li>
<li>Flutter — Improving rendering performance: <a href="https://docs.flutter.dev/perf/rendering-performance">https://docs.flutter.dev/perf/rendering-performance</a></li>
<li>Flutter — Optimizing performance in Flutter web apps with tree shaking and deferred loading (Flutter Material team series): <a href="https://docs.flutter.dev/perf/web-performance">https://docs.flutter.dev/perf/web-performance</a></li>
</ul>
<p><strong>WebAssembly and Renderers</strong></p>
<ul>
<li>Flutter — Support for WebAssembly (Wasm): <a href="https://docs.flutter.dev/platform-integration/web/wasm">https://docs.flutter.dev/platform-integration/web/wasm</a></li>
<li>Flutter — Build and release a web app (including <code>--wasm</code>): <a href="https://docs.flutter.dev/deployment/web">https://docs.flutter.dev/deployment/web</a></li>
</ul>
<p><strong>Deferred Loading / Code Splitting</strong></p>
<ul>
<li>Flutter — Deferred components for Android and web: <a href="https://docs.flutter.dev/perf/deferred-components">https://docs.flutter.dev/perf/deferred-components</a></li>
<li>Dart — Lazily loading a library (deferred imports): <a href="https://dart.dev/language/libraries#lazily-loading-a-library">https://dart.dev/language/libraries#lazily-loading-a-library</a></li>
<li><code>flutter_lazy_load_web</code> package: <a href="https://pub.dev/packages/flutter_lazy_load_web">https://pub.dev/packages/flutter_lazy_load_web</a></li>
</ul>
<p><strong>Offline-First Databases</strong></p>
<ul>
<li>Drift — Web setup (<code>sqlite3.wasm</code> + <code>drift_worker.js</code>): <a href="https://drift.simonbinder.eu/platforms/web/">https://drift.simonbinder.eu/platforms/web/</a></li>
<li>Drift — Official site: <a href="https://drift.simonbinder.eu/">https://drift.simonbinder.eu/</a></li>
<li><code>sqlite3.wasm</code> binaries (used by both Drift and sqflite web): <a href="https://github.com/simolus3/sqlite3.dart/releases">https://github.com/simolus3/sqlite3.dart/releases</a></li>
<li>Floor — Package: <a href="https://pub.dev/packages/floor">https://pub.dev/packages/floor</a></li>
<li><code>sqflite_common_ffi_web</code> — Experimental web implementation for sqflite/Floor: <a href="https://pub.dev/packages/sqflite_common_ffi_web">https://pub.dev/packages/sqflite_common_ffi_web</a></li>
</ul>
<p><strong>Supabase and Offline Sync</strong></p>
<ul>
<li>Supabase Flutter — Official package: <a href="https://pub.dev/packages/supabase_flutter">https://pub.dev/packages/supabase_flutter</a></li>
<li><code>synclayer_supabase</code> — Supabase adapter for SyncLayer (offline-first, delta sync, conflict resolution): <a href="https://pub.dev/packages/synclayer_supabase">https://pub.dev/packages/synclayer_supabase</a></li>
<li><code>supabase_flutter_ultra</code> — Offline-first wrapper with write queue and delta sync: <a href="https://pub.dev/packages/supabase_flutter_ultra">https://pub.dev/packages/supabase_flutter_ultra</a></li>
<li>PowerSync — Offline-first sync with Supabase (FlutterFlow integration guide): <a href="https://blog.flutterflow.io/introducing-a-local-database-that-auto-syncs-with-supabase/">https://blog.flutterflow.io/introducing-a-local-database-that-auto-syncs-with-supabase/</a></li>
<li><code>dynos_sync</code> — Drift + Supabase sync engine: <a href="https://pub.dev/packages/dynos_sync">https://pub.dev/packages/dynos_sync</a></li>
</ul>
<p><strong>Web Interop and Plugins</strong></p>
<ul>
<li>Dart — <code>dart:js_interop</code> documentation: <a href="https://dart.dev/interop/js-interop">https://dart.dev/interop/js-interop</a></li>
<li><code>package:web</code>: <a href="https://pub.dev/packages/web">https://pub.dev/packages/web</a></li>
</ul>
<p><strong>Articles and Community Guides</strong></p>
<ul>
<li>Flutter — Best practices for optimizing Flutter web loading speed: <a href="https://flutter.dev/blog/best-practices-for-optimizing-flutter-web-loading-speed">https://flutter.dev/blog/best-practices-for-optimizing-flutter-web-loading-speed</a></li>
<li>Colinchflutter — Optimizing Flutter web performance: <a href="https://colinchflutter.github.io/2023-09-26/10-10-14-784390-optimizing-flutter-web-performance/">https://colinchflutter.github.io/2023-09-26/10-10-14-784390-optimizing-flutter-web-performance/</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Modularising SwahiLib: How I Stopped Rebuilding My Whole App for One Line of Code]]></title><description><![CDATA[Slow build times pushed me into something I never wanted to do: modularising my simple dictionary app]]></description><link>https://siro.hashnode.dev/modularising-swahilib-how-i-stopped-rebuilding-my-whole-app-for-one-line-of-code</link><guid isPermaLink="true">https://siro.hashnode.dev/modularising-swahilib-how-i-stopped-rebuilding-my-whole-app-for-one-line-of-code</guid><category><![CDATA[Android]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Jetpack Compose]]></category><category><![CDATA[gradle]]></category><category><![CDATA[NowInAndroid]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Mon, 15 Jun 2026 06:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6152c793414f78089df39fdc/f47bf1c5-d945-4e2d-a6a7-5005bc978c45.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sometime back I posted on X about something every Android developer eventually has to deal once they start building an app: I would change just a line of code, and Gradle would simply decide to rebuild the app like forever.</p>
<p>I had just been from rewriting SwahiLib my Swahili dictionary app from Flutter to native using Kotlin and Jet Pack Compose. It was not easy and neither was it hard because I eventually finished it in record 4 months. I even wrote about the migration in my previous article: <a href="https://siro.hashnode.dev/rewriting-my-flutter-app-to-native-android-a-side-by-side-architecture-breakdown"><em>Rewriting My Flutter App to Native Android: A Side-by-Side Architecture Breakdown</em></a><em>.</em> This is a follow up on the article with a diversion into the already exhausted topic of modularising. I hope by the end of this article I would have impacted something on you.</p>
<hr />
<p>SwahiLib — the Swahili dictionary app I'd already rewritten once, from Flutter to native Kotlin and Compose — had grown into a single <code>app</code> module holding every screen, every repository, every database query. It worked. But sadly as Kotlin compilation would have it, it also meant that fixing a typo in the Proverb screen recompiled the Idiom screen, the Saying screen, the Word screen, and everything else along with it, every single time. I didn't know that precisely having been in Flutter developer for a long time since covid times until when I complained about it on X.</p>
<p><a class="embed-card" href="https://x.com/SiroFitsKe/status/2044658023436583303">https://x.com/SiroFitsKe/status/2044658023436583303</a></p>

<p>I remember Brian Orwe commenting with just one word: Kotlin to which I responded with ?? I think he thought I would figure it out once from just that but I needed more explanation. His next reply sent chills down my nerves into what I have been avoiding ever since I started writing Kotlin code for fun.</p>
<p><a class="embed-card" href="https://x.com/BrianOrwe/status/2044683909804294432?s=20">https://x.com/BrianOrwe/status/2044683909804294432?s=20</a></p>

<p>I told him I have always looked at that DroidconKe app and I just get tired to be honest of the numerous modules in it, I also looked at the codebase of Streeeka by one Mambo Brian and while both apps would build and run fine when all dependancies were fetched and gradle synced I just would not buy into the idea of modularising every single feature and so forth. I love simplicity esp for a small app like mine.</p>
<p>But Brian replied with the sentence that ended my denial:</p>
<blockquote>
<p>"Your app might be getting big that you might start to need to consider the complexity that comes with separation of modules as a reasonable consequence, verses long compile times because you changed a single line."</p>
</blockquote>
<p>After careful consideration, I knew he was right, which was almost the annoying part. I'd looked at modularising SwahiLib before and backed away from it every time. This is the story of the three weeks it took me to stop backing away.</p>
<h2>Why I'd Been Avoiding It</h2>
<p>I like simple apps. SwahiLib isn't huge — a dictionary of Swahili idioms, proverbs, sayings, and words, with search, history, a settings screen, a light/dark theme. One <code>app</code> module had been enough for a long time, and "enough" is a comfortable place to live.</p>
<p>The few times I'd poked at modularising, I'd gone looking at Google's <a href="https://github.com/android/nowinandroid">Now in Android</a> for a template, the way half of Android Twitter does. NiA is a genuinely excellent reference app — and a lot: convention plugins, Jacoco wiring, Spotless tasks, Gradle managed devices, a feature module split into API and implementation, a dependency graph generator. I'd get three folders deep and close it again, half convinced modularising meant all of that or none of it. For an app I maintain alone, that felt like the exact over-engineering I was trying to avoid. Brian's tweet didn't tell me anything I didn't already suspect — it just purged my excuse for sitting on it.</p>
<hr />
<h2>Attempt 1: Copying the Image, Not the Idea</h2>
<p>I began the difficult journey by opening a <code>feature-modularisation</code> branch and did what most people do the first time: split the app along the architecture diagram's layers. Four new modules went in — <code>core</code>, <code>data</code>, <code>domain</code>, <code>presentation</code> — and almost everything in <code>app</code> got moved into one of them. I was tired enough to call the commit <code>more modularisation</code>, which undersold it; it touched over 100 files in one go.</p>
<p>Each module, because Android Studio generates a full library module whether you need one or not, came with its own <code>AndroidManifest.xml</code>, launcher icons in five densities, light/dark theme XML, and a <code>proguard-rules.pro</code>. Four copies of an app icon nobody would ever see, since none of these were app modules. That should have been my first sign I was modularising by template rather than by need.</p>
<p>A week later I realized I needed a <code>build-logic</code> module — a convention-plugins setup, lifted straight from NiA: a Kotlin Gradle plugin per concern, so modules wouldn't repeat the same <code>android {}</code> block and dependency wiring. 30 files landed in one commit, titled simply <code>build-logic</code>, mostly airlifted, with a vague plan to trim what I didn't need once the build worked.</p>
<p>Friction came fast. I tried to finally get App Flavours done right, something I'd always wanted in every project. Somewhere in there, an entire <code>staging</code> flavour's resource set got deleted — icons, themes, manifest extras, all of it. Turns out you get a lot more opinionated about which module owns what once you're not the only module in the project.</p>
<p>A day later, exhausted but happy, I wrote the commit message I'm most fond of in this whole history: <code>getting it to run after a number of iterations</code>. 9 files massaged — the understatement doing a lot of work. The same day, <code>data</code> module lost about 20 files, most of which had landed only the day before: code I'd just relocated, deleted again because the dependency graph between modules wasn't behaving the way I'd assumed. Sentry broke in the move and had to be patched back before I could proceed further.</p>
<h2>The Reset</h2>
<p>The next day I did the thing that's hardest to do after a week and a half of work: I admitted attempt one wasn't working and reverted it. 2 PRs landed that day. The first <code>Fixing the modularisation</code> — generous, in hindsight. The second one was blunter: <code>removing core, data, domain and presentation modules</code>. Over 250 files were purged. Every module I'd built over the previous ten days, gone, back to the one <code>app</code> module.</p>
<p>I want to be honest about what that commit represents, because I think it's the most useful part of this whole story for anyone reading this before they start their own modularisation: the layer-based split — one module each for <code>core</code>, <code>data</code>, <code>domain</code>, <code>presentation</code> — didn't actually fix the problem that started this. <code>presentation</code> still held every screen in the app. Touching the Idiom screen still meant recompiling the module that also contained Proverb, Saying, Word, Home, and Settings. I'd added module boundaries without adding the isolation I was modularising <em>for</em>.</p>
<h2>Round 2: Hitting the Same Wall Again</h2>
<p>After a weekend cycling with friends, I started the week with <code>fixing ktor</code>, then the rebuild itself followed — committing <code>more modularisation</code> again, the exact same message that opened attempt one, like muscle memory typing itself in before I'd noticed. <code>core</code>, <code>data</code>, <code>domain</code>, <code>presentation</code> came back, this time with DI wired more sensibly per module instead of dumped into <code>core</code>. A cleaner version of the same shape. <code>fixing dependencies</code> closed out the day, the now-familiar texture of a networking layer arguing with module boundaries.</p>
<p>It was still wrong in the way that mattered: one <code>presentation</code> module, every screen inside it. I'd fixed the craftsmanship of attempt one without questioning its premise.</p>
<p>Then nothing for a week, despite doing things differently this time, save for that repeated commit message. I won't pretend the gap was strategic — I looked at the app, got tired the way I get tired of this particular problem, and did other things for a few days to relax from the whole modularisation headache.</p>
<hr />
<h2>What Actually Clicked</h2>
<p>More than a week later, I came back and landed the commit <code>fully modularised and done properly</code>. This time the message wasn't an understatement — it actually fixed the build-time problem I'd set out to solve in the first place.</p>
<blockquote>
<p>The fix wasn't <em>more</em> modules. It was modularising along a completely different axis.</p>
</blockquote>
<ul>
<li><p>Instead of cramming every screen into one <code>presentation</code> module, each screen got its own feature module.</p>
</li>
<li><p>Instead of one <code>core</code> module holding everything that wasn't UI, <code>core</code> got split by what each piece actually did — not by which architectural layer it was supposed to belong to.</p>
</li>
</ul>
<pre><code class="language-plaintext">app/
core/
  common/
  data/
  database/
  designsystem/
  network/
  ui/
feature/
  home/
  idiom/
  init/
  proverb/
  saying/
  settings/
  splash/
  word/
</code></pre>
<p><code>settings.gradle.kts</code> ended up reading roughly like this:</p>
<pre><code class="language-kotlin">include(":app")

include(":core:common")
include(":core:data")
include(":core:database")
include(":core:designsystem")
include(":core:network")
include(":core:ui")

include(":feature:home")
include(":feature:idiom")
include(":feature:init")
include(":feature:proverb")
include(":feature:saying")
include(":feature:settings")
include(":feature:splash")
include(":feature:word")
</code></pre>
<h2>The shape matters more than the names</h2>
<p>Feature modules don't need to depend on each other — <code>feature:idiom</code> has no business knowing <code>feature:word</code> exists. Each one only reaches down into the <code>core</code> modules it actually needs: <code>core:database</code> for Room, <code>core:network</code> for the API client, <code>core:designsystem</code> for shared theming, <code>core:ui</code> for shared composables and resources. <code>app</code> sits on top, wiring navigation between features and not doing much else.</p>
<p>This is the real difference from the previous 2 attempts:</p>
<blockquote>
<p>Gradle's incremental build only recompiles a module and whatever depends on it.</p>
</blockquote>
<ul>
<li><p>When every screen lived in <code>presentation</code>, any screen change recompiled <code>presentation</code> entirely.</p>
</li>
<li><p>Now, a change in <code>feature:idiom</code> only recompiles <code>feature:idiom</code> and <code>app</code>. <code>feature:word</code>, three folders over, stays untouched.</p>
</li>
</ul>
<blockquote>
<p>Generic names disappeared</p>
</blockquote>
<p><code>core</code>, <code>data</code>, and <code>domain</code> as catch-all labels are gone:</p>
<ul>
<li><p>The <code>domain</code> repository-interface layer — one interface, one implementation each — got dropped. It's ceremony that earns its keep at Google's scale and mostly adds indirection at mine.</p>
</li>
<li><p>What survived moved into modules named for what they actually hold: Room and its DAOs into <code>core:database</code>, the few genuinely cross-feature repositories (mainly theme preference) into <code>core:data</code>.</p>
</li>
</ul>
<p><code>build-logic</code> <strong>got pruned hard</strong> — the part of attempt one I'm glad I tried and glad I didn't keep, since trying it taught me what to cut.</p>
<p><strong>So what Survived?</strong> After all was done I remained with 4 plugins, applied consistently across fourteen modules:</p>
<ul>
<li><p>a library convention plugin</p>
</li>
<li><p>a Compose-library plugin</p>
</li>
<li><p>a single <code>AndroidFeatureConventionPlugin</code></p>
</li>
<li><p>a Hilt plugin —</p>
</li>
</ul>
<p><strong>What was cut?</strong> A lot of files, deleted in the commit that finally made it work:</p>
<ul>
<li><p>The app-level convention plugin (no point sharing config across app modules that don't exist).</p>
</li>
<li><p>The feature API/implementation split (built for cross-feature dependencies I don't have)</p>
</li>
<li><p>Jacoco, dedicated Lint and Test plugins,</p>
</li>
<li><p>Room and Networking plugins,</p>
</li>
<li><p>Subscriptions and Supabase plugins, a <code>RootPlugin</code></p>
</li>
<li><p>NiA's tooling helpers — badging, Gradle managed devices, dependency graph generator, Spotless.</p>
</li>
</ul>
<p>None of that tooling is bad — most of it's well built. It's tooling for a team, and I'm not one. Keeping it would have been the over-engineering I was avoiding all along, just arrived at by copying instead of choosing.</p>
<p><code>app</code> <strong>itself shrank to almost nothing:</strong> <code>MainActivity.kt</code>, the <code>SwahiLibApp</code> application class, a top-level <code>AppNavHost</code> stitching feature nav graphs together, and one DI module for network config. That's it. Everything else lives where it's used.</p>
<h2>The Last Mile</h2>
<p><code>fully modularised and done properly</code> is a confident commit message, and it was nearly true. A few loose ends followed. The cleanup that week had swept away <code>app/</code><a href="http://proguard-rules.pro"><code>proguard-rules.pro</code></a> along with everything else — and a release build doesn't run without it. Four files: one restored proguard config, one added release script.</p>
<p>Modularisation doesn't end on the commit where you say it's done. It ends a day later, when you try to ship a release build and find out what you forgot.</p>
<h2>Did Builds Actually Get Faster?</h2>
<p>I don't have a formal before-and-after benchmark to hand you, and I'd rather say that plainly than make up an impressive-sounding number for a blog post. What I can tell you, structurally, is why this should be faster — and it matches what I feel day to day: a one-line change in <code>feature:idiom</code> no longer has any reason to touch the other seven feature modules, since Gradle only recompiles a module and whatever sits above it in the dependency graph. Those eight modules can also compile in parallel rather than waiting on each other, since none of them depend on one another at all.</p>
<p>If you've got your own Gradle build scan or <code>--profile</code> output from a before-and-after like this, it's worth more than my word for it — paste it in below.</p>
<h2>Conclusion of the Matter</h2>
<p>A few things, if I could send them back to April 20th to my old me:</p>
<ul>
<li><p><strong>Modularise along feature boundaries first</strong> — that's the axis that actually isolates what gets rebuilt. Splitting by architectural layer instead (<code>core</code>, <code>data</code>, <code>domain</code>, <code>presentation</code>) looks like progress but barely touches build times, since all your screens are still sitting in one module together.</p>
</li>
<li><p><strong>Don't adopt a reference app's entire tooling surface</strong> just because it's the reference app. Convention plugins paid off almost immediately; Jacoco, Spotless, and managed-device test plugins didn't. The only way I found that line was installing all of it once and watching what I never touched.</p>
</li>
<li><p><strong>Expect to delete code you just wrote.</strong> The 252-file revert wasn't the project going wrong — it was the project finding its actual shape.</p>
</li>
<li><p><strong>Budget real calendar time</strong>, even for a small app. Hilt, KSP, and Gradle's manifest merger all have opinions about module boundaries you won't discover until you've crossed one.</p>
</li>
</ul>
<p>Brian Orwe, if you're reading this — thanks for the push. I'd have gotten here eventually, but probably not in three weeks, and probably not without a tweet in my replies reminding me I'd been avoiding it.</p>
]]></content:encoded></item><item><title><![CDATA[Rewriting my Flutter app to Native Android: A Side-by-Side Architecture Breakdown]]></title><description><![CDATA[How I rewrote SwahiLib from Flutter to native Android — BLoC vs ViewModel, Floor vs Room, get_it vs Hilt, with real code.]]></description><link>https://siro.hashnode.dev/rewriting-my-flutter-app-to-native-android-a-side-by-side-architecture-breakdown</link><guid isPermaLink="true">https://siro.hashnode.dev/rewriting-my-flutter-app-to-native-android-a-side-by-side-architecture-breakdown</guid><category><![CDATA[Android]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Jetpack Compose]]></category><category><![CDATA[architecture]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Wed, 15 Apr 2026 06:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6152c793414f78089df39fdc/f3febc6b-0640-49cb-9df4-19a055c8c7e1.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sometime last year I made a decision that a lot of Flutter developers may eventually face if they love to do hard stuff like I do: I rewrote <a href="https://play.google.com/store/apps/details?id=com.swahilib"><strong>SwahiLib</strong></a>, my Swahili dictionary app, from Flutter into native Android with Kotlin and Jetpack Compose. It wasn't a partial migration or a plugin-by-plugin sunset but a full rewrite, screen by screen, package by package.</p>
<p>This isn't a "Flutter bad, native good" post. Flutter did its job — it got SwahiLib to market fast and it ran the same logic on two platforms: Android and iOS with just one codebase. But after maintaining it for a while, I started covetting the things native side of the platforms gives you that Flutter, by design, can't:</p>
<ul>
<li><p>zero-bridge access to platform APIs,</p>
</li>
<li><p>a UI toolkit that's a first-class citizen instead of a guest renderer,</p>
</li>
<li><p>a much smaller gap between "what the OS does" and "what my code does."</p>
</li>
</ul>
<p>In this article I will be taking you through a practical, code-level comparison of the two codebases — same app, same features, same Supabase backend, two completely different architectures. I have written this from the perspective of someone who's done the rewrite, not someone theorizing about it, so I'll point out where native actually paid off and where it cost me something I didn't expect.</p>
<h2>The app, briefly</h2>
<p>SwahiLib is an <strong>offline-first Swahili dictionary (Kamusi)</strong> app that gives users access to <strong>words (maneno), idioms (nahau), proverbs (methali),</strong> and <strong>sayings (misemo)</strong>. After the install data is seeded locally, everything is searchable offline, allowing the app to work seamlessly without needing an internet connection.</p>
<p>The app uses <strong>Supabase</strong> as its <strong>BaaS</strong>. Data is fetched once, stored locally, and kept in sync through efficient batching and caching strategies. With a dataset of approximately <strong>17,000 records</strong>, optimizing local storage, search performance, and synchronization is a key part of the application's design.</p>
<p>Both the Android and iOS versions solve the same core challenges: fetching data from Supabase, persisting it locally, providing fast offline search and filtering, displaying detailed content screens, and supporting light and dark themes.</p>
<hr />
<h2>Start from dependencies: what each side actually pulls in</h2>
<p>This is the first thing that hits you when you put both <code>pubspec.yaml</code> and <code>build.gradle.kts</code> side by side — the <em>shape</em> of the dependency list tells you the architectural story before you've read a line of business logic.</p>
<p><strong>Flutter (</strong><code>pubspec.yaml</code><strong>):</strong></p>
<pre><code class="language-yaml">dependencies:
  floor: ^1.5.0                  # SQLite ORM with DAO pattern
  flutter_bloc: ^8.1.6            # State management
  get_it: ^8.0.2                  # Service locator
  injectable: ^2.5.0               # DI code generation on top of get_it
  http: ^1.3.0
  supabase_flutter: ^2.8.3
  sentry_flutter: ^8.5.0
  shared_preferences: ^2.3.3
  freezed_annotation: ^2.4.4       # Immutable unions for BLoC states/events
  json_annotation: ^4.9.0

dev_dependencies:
  build_runner: ^2.4.13
  floor_generator: ^1.5.0
  freezed: ^2.5.7
  json_serializable: ^6.8.0
</code></pre>
<p><strong>Kotlin (</strong><code>libs.versions.toml</code> <strong>/</strong> <code>build.gradle.kts</code><strong>):</strong></p>
<pre><code class="language-kotlin">implementation(libs.androidx.room.runtime)       // SQLite ORM
ksp(libs.androidx.room.compiler)
implementation(libs.hilt.android)                 // DI
ksp(libs.hilt.compiler)
implementation(libs.squareup.retrofit)
implementation(platform(libs.jan.tennert.supabase.bom))
implementation(libs.jan.tennert.supabase.postgrest)
implementation(libs.ktor.client.android)
implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.compose.bom...)      // Compose BOM + UI artifacts
alias(libs.plugins.io.sentry)
</code></pre>
<p>A few things jump out immediately:</p>
<ul>
<li><p><strong>One dependency manager generation step disappears entirely.</strong> Flutter's <code>floor</code>, <code>freezed</code>, and <code>json_serializable</code> all run through <code>build_runner</code>, a single generic codegen pipeline you invoke manually (<code>dart run build_runner build --delete-conflicting-outputs</code>) whenever you touch an annotated class. Kotlin's <code>ksp</code> (Kotlin Symbol Processing) does the equivalent for Room and Hilt, but it's wired directly into the Gradle build graph — it runs automatically as part of <code>compileDebugKotlin</code>, incrementally, without you remembering to invoke it. This sounds small until you've lost like 30 minutes to a stale <code>.g.dart</code> file that the IDE didn't regenerate in your Flutter build.</p>
</li>
<li><p><code>get_it</code> <strong>+</strong> <code>injectable</code> <strong>becomes just Hilt.</strong> In Flutter, dependency injection is two libraries glued together: <code>get_it</code> is the actual service locator, <code>injectable</code> is a code-gen layer that writes the registration boilerplate for you by scanning <code>@injectable</code>/<code>@singleton</code> annotations. In Kotlin, Hilt (which itself wraps Dagger) does both jobs as one Gradle plugin, and critically, it's <em>compile-time validated</em> — if you forget to provide a dependency, the build fails with a clear graph error instead of a runtime <code>Bad state: GetIt: Object/factory not registered</code>.</p>
</li>
<li><p><strong>No</strong> <code>freezed</code><strong>.</strong> Kotlin's <code>sealed class</code> / <code>data class</code> plus <code>when</code> exhaustiveness checking gives you most of what <code>freezed</code> exists to bolt onto Dart. You see this directly comparing state modeling below.</p>
</li>
<li><p><strong>RevenueCat and Billing only show up in the Kotlin version</strong> — not because Flutter couldn't do subscriptions, but because the rewrite was also the point where I added a paywall. Worth flagging so you don't think this is a 1:1 feature port; it's an architecture port with some product evolution riding along.</p>
</li>
</ul>
<h2>Dependency injection: <code>get_it</code>/<code>injectable</code> → Hilt</h2>
<p>Flutter's DI setup is annotation-driven but still requires an explicit init call at app startup:</p>
<pre><code class="language-dart">// injectable.dart
final getIt = GetIt.instance;

@InjectableInit(initializerName: r'initGetIt', generateForDir: ['lib'])
Future&lt;void&gt; configureDependencies(String environment) async {
  await getIt.initGetIt(environment: environment);
  await getIt.allReady();
}

@module
abstract class RegisterModule {
  @singleton
  @preResolve
  Future&lt;SharedPreferences&gt; prefRepo() =&gt; SharedPreferences.getInstance();

  @singleton
  @preResolve
  Future&lt;AppDatabase&gt; provideAppDatabase() async =&gt; await $FloorAppDatabase
      .databaseBuilder(await AppConstants.databaseFile)
      .build();
}
</code></pre>
<p>And then <code>main.dart</code> has to explicitly await it before <code>runApp()</code>:</p>
<pre><code class="language-dart">await configureDependencies(Environments.production);
await SentryFlutter.init(
  (options) =&gt; options.dsn = sentryUrl,
  appRunner: () =&gt; runApp(const SentryWidget(child: MyApp())),
);
</code></pre>
<p>In the Kotlin rewrite, Hilt modules just declare what they provide, and the Application class is a one-line annotation:</p>
<pre><code class="language-kotlin">@HiltAndroidApp
class SwahiLibApp : Application() {
    override fun onCreate() {
        super.onCreate()

        SentryAndroid.init(this) { options -&gt;
            options.dsn = BuildConfig.SentryDsn
            options.tracesSampleRate = 1.0
        }
    }
}
</code></pre>
<pre><code class="language-kotlin">@InstallIn(SingletonComponent::class)
@Module
object DatabaseModule {
    @Provides @Singleton
    fun provideDatabase(@ApplicationContext appContext: Context): AppDatabase =
        Room.databaseBuilder(appContext, AppDatabase::class.java, "SwahiliLibrary")
            .fallbackToDestructiveMigration()
            .build()

    @Provides
    fun provideWordDao(database: AppDatabase): WordDao = database.wordsDao()
}
</code></pre>
<p>There's no <code>await getIt.allReady()</code> step to forget, no async resolution race to reason about. Hilt builds the dependency graph at compile time via annotation processing, and <code>@AndroidEntryPoint</code> / <code>@HiltViewModel</code> wire injection directly into Android's component lifecycle (<code>Activity</code>, <code>ViewModel</code>) rather than through a global service locator you query manually. The trade-off: Hilt's compiler errors, when something's wrong, are notoriously verbose stack traces full of generated class names. <code>get_it</code>'s runtime failures are friendlier to read but you only find them by actually hitting that code path.</p>
<h2>Local persistence: Floor → Room</h2>
<p>This is the most mechanical migration in the entire rewrite, almost a find-and-replace, because <strong>Floor is explicitly modeled after Room</strong> — Marco Cattaneo built it that way on purpose for Flutter devs coming from Android. If you've ever wondered which one to learn first, it doesn't matter; the concepts transfer almost annotation-for-annotation.</p>
<pre><code class="language-dart">// Flutter — Floor
@Entity(tableName: AppConstants.wordsTable)
@JsonSerializable()
class Word {
  @PrimaryKey()
  int? id;
  int? rid;
  String? title;
  String? meaning;
  // ...
  factory Word.fromJson(Map&lt;String, dynamic&gt; json) =&gt; _$WordFromJson(json);
}

@dao
abstract class WordsDao {
  @Query('SELECT * FROM ${AppConstants.wordsTable} WHERE id = :id')
  Future&lt;Word?&gt; findWordById(int id);

  @Insert(onConflict: OnConflictStrategy.replace)
  Future&lt;void&gt; insertWord(Word word);

  @Query("DELETE FROM ${AppConstants.wordsTable}")
  Future&lt;void&gt; deleteAllWords();
}
</code></pre>
<pre><code class="language-kotlin">// Kotlin — Room
@Entity(tableName = Collections.WORDS, indices = [Index(value = ["rid"], unique = true)])
data class Word(
    @PrimaryKey val rid: Int = 0,
    @ColumnInfo(name = "title") val title: String? = null,
    @ColumnInfo(name = "meaning") val meaning: String? = null,
    // ...
) : Parcelable

@Dao
interface WordDao {
    @Query("SELECT * FROM ${Collections.WORDS} WHERE rid = :rid")
    fun getById(rid: String): Flow&lt;Word&gt;

    @Insert(onConflict = OnConflictStrategy.Companion.REPLACE)
    suspend fun insertAll(words: List&lt;Word&gt;)

    @Query("DELETE FROM ${Collections.WORDS}")
    fun delete()
}
</code></pre>
<p>Same <code>@Entity</code>/<code>@Dao</code>/<code>@Query</code> vocabulary, same <code>OnConflictStrategy</code>, same raw-SQL-with-named-params approach. Two differences worth calling out for anyone doing this migration:</p>
<ol>
<li><p><strong>Room DAOs return</strong> <code>Flow&lt;T&gt;</code> <strong>natively.</strong> Floor's DAO methods return <code>Future&lt;T&gt;</code> — a one-shot read. If you want reactive, auto-updating queries in Floor you have to explicitly declare a <code>Stream&lt;List&lt;Word&gt;&gt;</code> return type and Floor generates the observer wiring for you, which works but is opt-in per query. In Room, returning <code>Flow&lt;List&lt;Word&gt;&gt;</code> is the idiomatic default and plugs directly into <code>StateFlow</code>/Compose's <code>collectAsState()</code> with no extra ceremony.</p>
</li>
<li><p><code>@Parcelize</code> <strong>matters more on the native side</strong> because Compose Navigation passes complex objects between composables via <code>SavedStateHandle</code>, and <code>Word : Parcelable</code> makes that free. In Flutter, since everything lives in one Dart isolate by default, you just pass the object reference directly through the navigator — there's no parcel/serialize boundary to cross for in-process navigation.</p>
</li>
</ol>
<h2>Networking and sync: same Supabase, different client ergonomics</h2>
<p>Both versions talk to the same Postgres-backed Supabase project, but the client libraries shape the sync code differently. Here's the Flutter side syncing on first launch:</p>
<pre><code class="language-dart">class DataInitBloc extends Bloc&lt;DataInitEvent, DataInitState&gt; {
  DataInitBloc() : super(const _DataInitState()) {
    on&lt;FetchData&gt;(_onFetchData);
    on&lt;SaveData&gt;(_onSaveData);
  }

  final _dataRepo = DataInitRepository(Supabase.instance.client);

  void _onFetchData(FetchData event, Emitter&lt;DataInitState&gt; emit) async {
    emit(const DataInitProgressState());
    final idioms = await _dataRepo.fetchIdioms();
    final proverbs = await _dataRepo.fetchProverbs();
    final sayings = await _dataRepo.fetchSayings();
    final words = await _dataRepo.fetchWords();
    emit(DataInitFetchedState(idioms, proverbs, sayings, words));
  }

  void _onSaveData(SaveData event, Emitter&lt;DataInitState&gt; emit) async {
    emit(const DataInitSavingState('Inapakia maneno (words) 16,641 ...', 0));
    for (int i = 0; i &lt; event.words.length; i++) {
      int progress = (i / event.words.length * 100).toInt();
      emit(DataInitSavingState(progressDesc(progress), progress));
      await _dbRepo.saveWord(event.words[i]);
    }
    // ...repeats for idioms, proverbs, sayings
    emit(const DataInitSavedState());
  }
}
</code></pre>
<p>And the Kotlin equivalent, using the <a href="https://github.com/supabase-community/supabase-kt">supabase-kt</a> client (the official Kotlin Multiplatform SDK, built on Ktor):</p>
<pre><code class="language-kotlin">@HiltViewModel
class InitViewModel @Inject constructor(
    private val idiomRepo: IdiomRepo,
    private val proverbRepo: ProverbRepo,
    private val sayingRepo: SayingRepo,
    private val wordRepo: WordRepo,
    private val prefsRepo: PrefsRepo,
) : ViewModel() {
    fun initialize(context: Context) {
        viewModelScope.launch {
            _uiState.emit(UiState.Loading)
            if (NetworkUtils.isNetworkAvailable(context)) {
                val idiomInit = async { idiomRepo.fetchRemoteData() }
                val proverbInit = async { proverbRepo.fetchRemoteData() }
                val sayingInit = async { sayingRepo.fetchRemoteData() }
                val wordInit = async { wordRepo.fetchRemoteData() }

                idiomInit.await(); proverbInit.await()
                sayingInit.await(); wordInit.await()

                prefsRepo.isDataLoaded = true
                _uiState.emit(UiState.Saved)
            } else {
                _uiState.emit(UiState.Error("Loo! Hapa bila muunganisho wa intaneti..."))
            }
        }
    }
}
</code></pre>
<p>And inside <code>WordRepo</code>, the actual fetch is paginated explicitly against Supabase's <code>range()</code>:</p>
<pre><code class="language-kotlin">suspend fun fetchRemoteData(): Result&lt;Int&gt; = withContext(Dispatchers.IO) {
    runCatching {
        var offset = 0L
        val pageSize = 2000
        val allWords = mutableListOf&lt;Word&gt;()

        while (true) {
            val batch = supabase[Collections.WORDS]
                .select { range(offset, offset + pageSize - 1) }
                .decodeList&lt;WordDto&gt;()
            if (batch.isEmpty()) break
            allWords.addAll(batch.map(MapDtoToEntity::mapToEntity))
            if (batch.size &lt; pageSize) break
            offset += pageSize
        }
        saveWords(allWords)
        Result.success(allWords.size)
    }.getOrElse { Result.failure(it) }
}
</code></pre>
<p>The structural difference here isn't really Flutter-vs-Kotlin, it's <strong>BLoC-vs-coroutines</strong> as a concurrency model:</p>
<ul>
<li><p>BLoC's <code>_onSaveData</code> runs the four-collection save <strong>sequentially</strong>, <code>await</code>-ing each <code>_dbRepo.save*()</code> call in a loop, because events in a single BLoC handler execute linearly by design — that's the whole point of the event-to-state contract.</p>
</li>
<li><p>The Kotlin version explicitly fires all four <code>fetchRemoteData()</code> calls as <strong>parallel coroutines</strong> via <code>async {}</code>, then <code>await()</code>s them together. <code>kotlinx.coroutines</code> structured concurrency makes "do these four things at once, then proceed" a two-line pattern. You <em>can</em> do this in Dart with <code>Future.wait([...])</code>, but BLoC's event-driven model doesn't naturally nudge you toward it the way <code>viewModelScope.launch { async {} }</code> does — I had to deliberately reach for it.</p>
</li>
</ul>
<p>This wasn't a one-for-one logic port — it's a case where the new architecture made an actual performance improvement (parallel fetch instead of serial) obvious enough that I took it. That's worth calling out to readers planning their own rewrite: <strong>don't treat the rewrite as a literal transliteration</strong>. Use it as a checkpoint to fix things you'd accumulated debt around.</p>
<h2>State management: BLoC events/states → ViewModel + StateFlow</h2>
<p>This is the part of the migration that touches the most files, because it's the core interaction pattern for every single screen.</p>
<p>Flutter's BLoC pattern is explicitly an event-sourcing-flavored state machine: you define a sealed <code>Event</code> type, a sealed <code>State</code> type, and a <code>Bloc</code> that maps one to the other.</p>
<pre><code class="language-dart">@freezed
sealed class HomeEvent with _$HomeEvent {
  const factory HomeEvent.fetchData() = FetchData;
  const factory HomeEvent.appUpdates() = CheckAppUpdates;
}

class HomeBloc extends Bloc&lt;HomeEvent, HomeState&gt; {
  HomeBloc() : super(const _HomeState()) {
    on&lt;FetchData&gt;(_onFetchData);
    on&lt;CheckAppUpdates&gt;(_onCheckAppUpdates);
  }

  final _dbRepo = getIt&lt;DatabaseRepository&gt;();

  void _onFetchData(FetchData event, Emitter&lt;HomeState&gt; emit) async {
    emit(const HomeProgressState());
    final idioms = await _dbRepo.fetchIdioms();
    final proverbs = await _dbRepo.fetchProverbs();
    final sayings = await _dbRepo.fetchSayings();
    final words = await _dbRepo.fetchWords();
    emit(HomeFetchedDataState(idioms, proverbs, sayings, words));
  }
}
</code></pre>
<p>The widget dispatches events (<code>context.read&lt;HomeBloc&gt;().add(const HomeEvent.fetchData())</code>) and rebuilds via <code>BlocBuilder</code> listening for new states. It's explicit, testable, and the <code>freezed</code> union types give you exhaustive <code>switch</code> matching — but every single screen needs its own <code>Event</code> file, <code>State</code> file, and <code>Bloc</code> file (three files minimum, often four with the generated <code>.freezed.dart</code>), and the indirection between "user did X" → "event fired" → "handler ran" → "new state emitted" → "widget rebuilt" is a lot of hops for what's often a direct cause-and-effect.</p>
<p>The Kotlin/Compose version collapses that into a <code>ViewModel</code> exposing <code>StateFlow</code> directly, no separate event type required for simple cases:</p>
<pre><code class="language-kotlin">@HiltViewModel
class HomeViewModel @Inject constructor(
    private val idiomRepo: IdiomRepo,
    private val proverbRepo: ProverbRepo,
    private val sayingRepo: SayingRepo,
    private val wordRepo: WordRepo,
    private val prefsRepo: PrefsRepo,
) : ViewModel() {
    private val _uiState: MutableStateFlow&lt;UiState&gt; = MutableStateFlow(UiState.Loading)
    val uiState: StateFlow&lt;UiState&gt; = _uiState.asStateFlow()

    private val _allWords = MutableStateFlow&lt;List&lt;Word&gt;&gt;(emptyList())
    private val _filteredWords = MutableStateFlow&lt;List&lt;Word&gt;&gt;(emptyList())
    val filteredWords: StateFlow&lt;List&lt;Word&gt;&gt; get() = _filteredWords

    fun fetchData() {
        _uiState.tryEmit(UiState.Loading)
        viewModelScope.launch {
            _allWords.value = wordRepo.fetchLocalData()
            _filteredWords.value = _allWords.value
            // ...idioms, proverbs, sayings follow the same shape
            _uiState.tryEmit(UiState.Filtered)
        }
    }

    fun filterData(tab: HomeTab, qry: String) {
        val query = qry.lowercase()
        when (tab) {
            HomeTab.Words -&gt; _filteredWords.value =
                _allWords.value.filter { it.title?.startsWith(query) == true }
            // ...
        }
    }
}
</code></pre>
<p>And <code>UiState</code> is just a plain sealed class — no annotation processor required:</p>
<pre><code class="language-kotlin">sealed class UiState {
    object Idle : UiState()
    object Loading : UiState()
    object Loaded : UiState()
    object Filtered : UiState()
    class Error(val message: String) : UiState()
}
</code></pre>
<p>The Compose screen calls <code>viewModel.fetchData()</code> directly (a method call, not an event dispatch) and observes <code>uiState.collectAsState()</code>. There's no <code>Emitter</code>, no separate <code>Event</code> sealed class to maintain in lockstep with the <code>State</code> one, and Kotlin's <code>when</code> over a <code>sealed class</code> gives you the same compiler-enforced exhaustiveness <code>freezed</code>'s pattern matching gives Dart — just without a code generation step.</p>
<p><strong>Trade-off to be honest about:</strong> BLoC's event/state separation genuinely shines in larger apps with complex cross-cutting concerns (undo/redo, event replay, time-travel debugging, multiple producers feeding one consumer). For a CRUD-shaped dictionary app like SwahiLib, that separation was overhead I was paying for without using. If your app's domain logic is <em>actually</em> event-sourced in nature, don't read this section as "ViewModel is strictly simpler" — it's simpler for <em>this</em> shape of app.</p>
<h2>Navigation: imperative <code>Navigator</code> → Compose <code>NavHost</code></h2>
<p>Flutter's navigation here uses the classic imperative <code>Navigator</code> with named routes and a manual route table:</p>
<pre><code class="language-dart">static Route? onGenerateRoute(RouteSettings settings) {
  final Map&lt;String, WidgetBuilder&gt; routes = {
    RouteNames.splash: (context) =&gt; const SplashScreen(),
    RouteNames.dataInit: (context) =&gt; const DataInitScreen(),
    RouteNames.home: (context) =&gt; const HomeScreen(),
    RouteNames.settings: (context) =&gt; const SettingsScreen(),
  };
  // ...
}
</code></pre>
<p>Compose Navigation is declarative and, importantly, ties each route directly to its <code>hiltViewModel()</code> instance and typed arguments:</p>
<pre><code class="language-kotlin">@Composable
fun AppNavHost(navController: NavHostController = rememberNavController(), themeRepo: ThemeRepository) {
    NavHost(navController = navController, startDestination = Routes.SPLASH) {
        composable(Routes.SPLASH) {
            val viewModel: SplashViewModel = hiltViewModel()
            SplashScreen(navController = navController, viewModel = viewModel)
        }
        composable(route = Routes.WORD) {
            val word = navController.previousBackStackEntry
                ?.savedStateHandle?.get&lt;Word&gt;("word")
            val viewModel: WordViewModel = hiltViewModel()
            WordScreen(navController = navController, viewModel = viewModel, word = word)
        }
        // ...
    }
}
</code></pre>
<p>Two things changed in practice: passing a full <code>Word</code> object between screens now goes through <code>SavedStateHandle</code> (hence <code>@Parcelize</code> on every entity, mentioned earlier), and every screen gets its <code>ViewModel</code> scoped automatically to its <code>NavBackStackEntry</code> via <code>hiltViewModel()</code> — no manual <code>BlocProvider</code> wrapping needed at each route like the Flutter <code>RepositoryProvider</code>/<code>BlocProvider</code> widgets required.</p>
<h2>Theming: <code>ThemeData</code> → Material 3 <code>ColorScheme</code></h2>
<p>Both apps are light/dark themeable, but the Kotlin rewrite moved onto full Material 3 semantic tokens (<code>primaryContainer</code>, <code>surfaceVariant</code>, <code>outlineVariant</code>, etc.) rather than Flutter's older-style <code>ColorScheme</code> with fewer slots:</p>
<pre><code class="language-dart">static ThemeData lightTheme() {
  return ThemeData(
    scaffoldBackgroundColor: ThemeColors.accent1,
    colorScheme: const ColorScheme(
      brightness: Brightness.light,
      primary: ThemeColors.primary,
      secondary: ThemeColors.primary1,
      surface: Colors.white,
      // fewer semantic slots
    ),
  );
}
</code></pre>
<pre><code class="language-kotlin">private val LightTheme = lightColorScheme(
    primary = LightColors.primary,
    primaryContainer = LightColors.primaryContainer,
    secondary = LightColors.secondary,
    secondaryContainer = LightColors.secondaryContainer,
    tertiary = LightColors.tertiary,
    surfaceVariant = LightColors.surfaceVariant,
    outlineVariant = LightColors.outlineVariant,
    inverseSurface = LightColors.inverseSurface,
    // ...full Material 3 token set
)
</code></pre>
<p>This isn't strictly a Flutter-vs-Kotlin limitation — <code>flutter_bloc</code>'s <code>ThemeData</code> <em>can</em> express the full Material 3 token set too. It's more that rebuilding the theme from scratch was a natural moment to adopt the fuller token system properly rather than carry forward a partial one.</p>
<h2>What I actually gained going native</h2>
<ul>
<li><p><strong>No bridge, no engine.</strong> Flutter renders through its own Skia/Impeller engine sitting on top of a platform view — gorgeous for consistency across iOS/Android, but it means every "drop down to platform" moment (deep notification customization, certain WorkManager edge cases, specific widget/launcher integrations) goes through a <code>MethodChannel</code>. In native Kotlin, there's no channel to cross. <code>Room</code>, <code>WorkManager</code>, <code>Compose</code> — all the same process, same language, same stack trace.</p>
</li>
<li><p><strong>Faster runtime, smaller footprint.</strong> No bundled Flutter engine binary, no Skia/Impeller runtime shipped inside the APK. The app is leaner and cold-start is noticeably snappier.</p>
</li>
<li><p><strong>Debugging is one language stack, not two.</strong> Flutter's stack traces routinely span Dart and, when something breaks at the platform boundary, Kotlin/Java/ObjC underneath. Native Android crashes are Kotlin top to bottom (with Java interop, but no cross-VM bridge).</p>
</li>
<li><p><strong>First-class access to new platform APIs on day one.</strong> Compose's Material 3 components, Predictive Back, the new edge-to-edge APIs — I'm not waiting on a Flutter plugin maintainer to wrap them.</p>
</li>
</ul>
<h2>The honest trade-off nobody warns you about: build times</h2>
<p>Here's the promise I made you in the title, and I'm not going to bury it.</p>
<p>After several months of writing Kotlin full-time on this project, the app <em>runs</em> faster — but <strong>building it is not faster than Flutter was</strong>. Flutter's hot reload, for all its engine overhead at runtime, gives you sub-second iteration on UI changes. Kotlin with KSP processing Room and Hilt annotations, plus Compose's own compiler plugin doing work on every recomposition-touching change, means a meaningfully slower edit-compile-deploy loop, especially on a clean or non-incremental build.</p>
<p>This isn't a "Kotlin the language is slow" problem — it's a <strong>single-module Gradle build</strong> problem. Right now this entire app lives in one <code>:app</code> module, which means Gradle has no way to parallelize compilation across independent units of code, and KSP has to reprocess the whole annotation graph more often than it should. The fix for this is well-established in the native Android world: splitting the codebase into Gradle modules (<code>:core</code>, <code>:data</code>, <code>:domain</code>, <code>:presentation</code>, feature modules) so Gradle's build cache and parallel task execution actually have something to work with.</p>
<p>I was already feeling the cost of not having done this from day one, and it became the next piece of work on this app. So — I'll be back with a follow-up article specifically on modularizing this Android codebase the proper way, and what it actually does to build times once <code>:app</code> stops being a single monolithic compilation unit. If you're rewriting your own Flutter app to native and want to skip the pain I'm about to go fix, that's the one to watch for.</p>
<h2>If you're planning this migration yourself</h2>
<p>A short, opinionated checklist based on what actually mattered here:</p>
<ol>
<li><p><strong>Start from dependencies, not screens.</strong> Map your Flutter package list to its native equivalent first (Floor→Room, get_it+injectable→Hilt, flutter_bloc→ViewModel+StateFlow). It tells you the shape of the rewrite before you write a line of UI.</p>
</li>
<li><p><strong>Don't transliterate BLoC into ViewModel 1:1.</strong> If your states don't need event-sourcing semantics, you're carrying unnecessary indirection forward. Let the new architecture simplify what it can.</p>
</li>
<li><p><strong>Use the parallelism the new concurrency model gives you for free.</strong> <code>async {}</code> + <code>awaitAll()</code> in coroutines is a much shorter path to "do these in parallel" than most BLoC-shaped code naturally falls into.</p>
</li>
<li><p><strong>Plan your module structure before you have 80+ files in one</strong> <code>:app</code> <strong>module.</strong> I didn't, and I'm paying for it in build times right now. Don't repeat that mistake — or if you do, budget time to fix it early rather than late.</p>
</li>
<li><p><strong>Treat the rewrite as a checkpoint, not a port.</strong> This is your one good opportunity to fix accumulated debt — theming systems, half-finished token sets, repository patterns you were never happy with. I took it for the Material 3 token migration and the parallel sync fetch; you'll find your own equivalents.</p>
</li>
</ol>
<p>That's the architecture side of the story. Next up: modularizing this thing properly and finding out how much of that build-time pain Gradle modules actually buy back.</p>
<hr />
<p><a href="https://github.com/SiroDevs/SwahiLib-Flutter">SwahiLib-Flutter Github Repo</a></p>
<p><a href="https://github.com/SiroDevs/SwahiLib-Android/tree/beta">SwahiLib-Android Github Repo</a></p>
<p><a href="https://play.google.com/store/apps/details?id=com.swahilib">Android App on Google PlayStore</a></p>
<hr />
<p><em>SwahiLib is a Swahili-English dictionary app for Android, built under the Futuristic Ke brand by me. If you're going through a similar Flutter-to-native migration and want to compare notes, find me in the comments.</em></p>
]]></content:encoded></item><item><title><![CDATA[What's In Store in Flutter 3.35]]></title><description><![CDATA[🚀 Web Hot Reload, Widget Previews, Multi-window Apps, AI & More!]]></description><link>https://siro.hashnode.dev/whats-in-store-in-flutter-3-35</link><guid isPermaLink="true">https://siro.hashnode.dev/whats-in-store-in-flutter-3-35</guid><category><![CDATA[Flutter]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Android]]></category><category><![CDATA[desktop]]></category><category><![CDATA[Dart]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Sun, 17 Aug 2025 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755581579533/1c52a8bc-71b7-414b-a0a5-b939a5fe1a97.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Flutter keeps evolving, and this latest release is all about helping you <strong>build faster, smarter, and with more polish than ever before</strong>. With <strong>over 1100 commits from 168 contributors (including 39 first-timers!)</strong>, Flutter 3.35 is another huge step forward for the framework and the community behind it.</p>
<p>This update is packed with features that make your day-to-day development smoother — from <strong>stateful hot reload on the web now being stable by default</strong>, to the <strong>experimental launch of Widget Previews</strong>, giving you a whole new way to design, test, and iterate on your widgets. On top of that, the team has shipped <strong>important accessibility upgrades, refined core widgets, boosted engine performance, and expanded IDE support</strong> to bring a more seamless experience across platforms.</p>
<p>In short, Flutter 3.35 is built to <strong>supercharge your productivity while raising the bar on quality and accessibility</strong>. Whether you’re coding your first app or scaling a product across platforms, this release has something for you. Let’s dive in and see what’s new!</p>
<h2 id="heading-web">Web</h2>
<h3 id="heading-hot-reload-on-the-web-now-default">⚡ Hot Reload on the Web — Now Default!</h3>
<p>One of Flutter’s most-loved features has officially leveled up: <strong>stateful hot reload is now stable and enabled by default on the web</strong>. No more flags, no extra steps — just build, save, and watch your changes come to life instantly in the browser.</p>
<p>This milestone was made possible thanks to the incredible feedback from the community during the experimental phase. The Dart and Flutter web teams took that input, ironed out performance issues, and expanded the range of code changes that can be hot reloaded successfully.</p>
<p>The vision? A <strong>seamless hot reload experience across every platform</strong>, so whether you’re building for mobile, desktop, or web, you enjoy the same snappy workflow. While you <em>can</em> still turn it off with <code>--no-web-experimental-hot-reload</code>, the plan is to eventually make it a permanent default.</p>
<h3 id="heading-wasm-dry-runs">🧪 Wasm Dry Runs</h3>
<p>Another big step forward is preparation for <strong>WebAssembly (Wasm)</strong> as Flutter’s default web build target. Starting in 3.35, every JavaScript build now includes a <strong>“dry run” compilation to Wasm</strong>.</p>
<p>What does that mean? Flutter checks if your app is Wasm-ready and reports any warnings straight to the console. Think of it as a friendly heads-up before the full switch happens. You can control this behavior with the <code>--wasm-dry-run</code> or <code>--no-wasm-dry-run</code> flags.</p>
<h3 id="heading-so-what-changed">🎉 So What Changed?</h3>
<p><strong>Before:</strong> You had to manually enable web hot reload with the experimental flag.<br /><strong>Now:</strong> Just run your app in dev mode (from the command line, IDE, or coding agent) and hot reload is ready out of the box.</p>
<p>The <code>--web-experimental-hot-reload</code> flag is now history — feel free to remove it from your configs.</p>
<p>By the way since every developer’s environment is unique, you can temporarily disable it using <code>--no-web-experimental-hot-reload</code>, if hot reload causes hiccups in your setup.</p>
<h2 id="heading-desktop">Desktop</h2>
<h3 id="heading-multi-window-support-takes-shape">🪟 Multi-Window Support Takes Shape</h3>
<p>Good news to those developers building desktop experiences with Flutter like myself and 99 others: <strong>multi-window support is officially on the horizon</strong> thanks to <strong>Canonical</strong>! 🎉</p>
<p>This release includes the <strong>foundational engine logic</strong> needed to create and update multiple windows on <strong>Windows and macOS</strong>.</p>
<p>What does this mean for you? It’s the first big step toward letting your Flutter apps open more than one window at a time — think productivity tools, editors, or any app where extra screens boost the user experience.</p>
<p>And this is just the beginning:</p>
<ul>
<li><p>Future updates will extend support to <strong>Linux</strong>, completing the desktop trifecta.</p>
</li>
<li><p>Experimental APIs are coming soon, so you can start playing around with multi-window functionality directly from your app code.</p>
</li>
</ul>
<h2 id="heading-android">Android</h2>
<h3 id="heading-sensitive-content-protection">Sensitive Content Protection</h3>
<p>Flutter on Android now supports <strong>protecting sensitive user content</strong> during media projection. With API 35+, you can use the new <code>SensitiveContent</code> widget to automatically obscure your app’s screen during screen shares — helping safeguard private data from exposure or theft.</p>
<p>See the <a target="_blank" href="https://docs.flutter.dev/platform-integration/android/sensitive-content">Protect sensitive content guide</a> for setup instructions.</p>
<h2 id="heading-ios">iOS</h2>
<h3 id="heading-ios-26-xcode-26-beta-compatibility">iOS 26 + Xcode 26 Beta Compatibility</h3>
<p>The Flutter team is actively preparing Flutter for <a target="_blank" href="https://www.apple.com/os/ios/"><strong>iOS 26 and Xcode 26 beta</strong></a>. You can help accelerate this effort by testing your apps and plugins against the latest betas and filing any issues you encounter.</p>
<p>To follow our progress (including a running list of known issues), check out the dedicated <a target="_blank" href="https://github.com/orgs/flutter/projects/212">GitHub project</a>.</p>
<h2 id="heading-engine-updates">Engine Updates</h2>
<p>Flutter <strong>3.35</strong> brings a series of performance and compatibility improvements across Impeller, iOS, Android, and desktop platforms.</p>
<h3 id="heading-general-engine">🔧 General Engine</h3>
<ul>
<li><p><strong>Optimization</strong>: Faster app startup thanks to initialization on a new thread.</p>
</li>
<li><p><strong>iOS</strong>: Adoption of <code>UISceneDelegate</code> for better integration with iOS app lifecycles.</p>
</li>
<li><p><strong>Tooling</strong>: Additional improvements to support debugging and profiling.</p>
</li>
</ul>
<h3 id="heading-impeller">🎨 Impeller</h3>
<ul>
<li><p><strong>Optimization</strong>: MSAA has been removed from intermediate targets for efficiency.</p>
</li>
<li><p><strong>Fidelity</strong>: Lossy texture has been disabled compression by default.</p>
</li>
<li><p><strong>Fix</strong>: Resolved a stuck <code>MaskFilter</code> bug in certain draw calls.</p>
</li>
<li><p><strong>Optimization</strong>: Path rendering performance has been improved.</p>
</li>
<li><p><strong>New</strong>: Support for Vivante GPUs has been added.</p>
</li>
<li><p><strong>Optimization</strong>: <code>DrawImageNine</code> now uses faster Porter-Duff blends.</p>
</li>
<li><p><strong>Fix</strong>: Patched memory leak in VideoPlayer.</p>
</li>
<li><p><strong>Fidelity</strong>: Enhanced blur radius calculation for smoother large-sigma blurs.</p>
</li>
</ul>
<h3 id="heading-ios-1">🍏 iOS</h3>
<ul>
<li><p><strong>Fix</strong>: Crash resolved in <code>WebView</code> when wrapped with <code>ClipRSuperellipse</code>.</p>
</li>
<li><p><strong>Fix</strong>: Image decoding now works when apps launch from background notifications.</p>
</li>
<li><p><strong>Revival</strong>: <strong>Live Text (OCR)</strong> support returns to text fields.</p>
</li>
</ul>
<h3 id="heading-android-1">🤖 Android</h3>
<ul>
<li><p><strong>Migration</strong>: Default template upgraded to Android 24.</p>
</li>
<li><p><strong>Fix</strong>: Crash fix for Android ≤ 14 caused by background image readers.</p>
</li>
<li><p><strong>Fix</strong>: Corrected uniform array handling in OpenGLES Impeller shaders.</p>
</li>
<li><p><strong>Fix</strong>: Upside-down fragment shader passes in OpenGLES Impeller.</p>
</li>
<li><p><strong>Fix</strong>: Crash fix in <code>FlutterEngineGroup</code>.</p>
</li>
</ul>
<h3 id="heading-macos">💻 macOS</h3>
<ul>
<li><strong>Fix</strong>: Corrected <strong>Display P3 color rendering</strong> for higher visual fidelity.</li>
</ul>
<h2 id="heading-devtools-and-ides"><strong>DevTools and IDEs</strong></h2>
<h3 id="heading-dart-amp-flutter-mcp-server-graduates-to-stable">Dart &amp; Flutter MCP Server graduates to stable 🎉</h3>
<p>The Flutter team just announced that the <strong>Dart and Flutter MCP Server is now available in the stable channel of the Dart SDK</strong>! 🎊 This is a huge step forward in the vision of building a <strong>highly productive, AI-assisted development experience</strong> for every Dart as well as a Flutter developer.</p>
<h4 id="heading-ai-with-context-not-just-autocomplete">AI with context — not just autocomplete</h4>
<p>The Dart and Flutter MCP Server acts as a <strong>bridge between your project and AI coding assistants</strong>, giving them far richer context than ever before. Instead of just suggesting snippets, your AI assistant can now <em>understand</em> your project structure, dependencies, and widget tree — and even take meaningful actions on your behalf.</p>
<p>With the MCP Server, you can ask your AI assistant to:</p>
<ul>
<li><p>🛠️ <strong>Fix runtime errors</strong>: Inspect the live widget tree, spot a <code>RenderFlex</code> overflow, and apply the right fix automatically.</p>
</li>
<li><p>📦 <strong>Manage dependencies</strong>: Find the best package on <a target="_blank" href="http://pub.dev">pub.dev</a>, add it to <code>pubspec.yaml</code>, and run <code>pub get</code> — all in one step.</p>
</li>
<li><p>✍️ <strong>Write and correct code</strong>: Generate boilerplate for a new feature, then self-correct any analysis errors it introduces.</p>
</li>
</ul>
<h4 id="heading-works-with-your-favorite-ai-tools">Works with your favorite AI tools 🤝</h4>
<p>You can connect the MCP Server to tools like <strong>Gemini Code Assist, Firebase Studio, Gemini CLI, GitHub Copilot, and Cursor</strong> — unlocking a new level of productivity with the editor you already love.</p>
<p>👉 To dive deeper, you can check out the guide here: <a target="_blank" href="https://medium.com/flutter/supercharge-your-dart-flutter-development-experience-with-the-dart-mcp-server-2edcc8107b49"><em>Supercharge Your Dart &amp; Flutter Development Experience with the Dart and Flutter MCP Server</em></a>.</p>
<h3 id="heading-devtools-keeps-getting-better">DevTools keeps getting better 🛠️</h3>
<p>The Flutter team has continued polishing DevTools with <strong>UX improvements, performance boosts, and bug fixes</strong> across versions <a target="_blank" href="https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.46.0">2.46.0</a>, <a target="_blank" href="https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.47.0">2.47.0</a>, and <a target="_blank" href="https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.48.0">2.48.0</a>. Full details are in the release notes.</p>
<h3 id="heading-widget-previews">Widget Previews</h3>
<p>One of the most requested features from the community is finally here — <strong>Widget Previews (experimental)</strong> are now available in stable!</p>
<p>Hot reload is still a developer’s best friend when iterating quickly, but Widget Previews take it a step further:</p>
<ul>
<li><p>🖼️ Preview widgets in <strong>isolation</strong>, separate from a full app.</p>
</li>
<li><p>🎨 Test components across different screen sizes, themes, and text scales — side by side.</p>
</li>
<li><p>⚡ Perfect for building out design systems or verifying complex UI variations quickly.</p>
</li>
</ul>
<p>This is an <strong>early experimental release</strong>, and your feedback will directly shape its future. The Flutter team is already planning:</p>
<ul>
<li><p>✨ <strong>Tighter IDE integration</strong> — embed previews directly inside VS Code, Android Studio, and IntelliJ.</p>
</li>
<li><p>🎨 <strong>Generic theming support</strong> — as the team decouples Material and Cupertino libraries, Widget Previews will adapt to any design system.</p>
</li>
</ul>
<p>👉 Try it now: <a target="_blank" href="https://docs.flutter.dev/development/tools/widget-previewer">Getting Started with Flutter Widget Previewer (Experimental)</a></p>
<h3 id="heading-faster-analysis-with-aot-snapshot">Faster analysis with AOT snapshot ⚡</h3>
<p>The <code>dart</code> command-line tool now uses the <strong>AOT-compiled analysis server snapshot</strong> for commands like <code>dart analyze</code>, <code>dart fix</code>, and <code>dart language-server</code>.</p>
<p>What this means for you:</p>
<ul>
<li><p>✅ <strong>No functional change</strong> — but a big performance win.</p>
</li>
<li><p>📈 In tests, short commands finish in a fraction of the time.</p>
</li>
<li><p>⏩ Long-running tasks like project analysis are <strong>nearly 50% faster</strong>.</p>
</li>
</ul>
<p>This makes everyday workflows — including CI test runs — feel much snappier.</p>
<h3 id="heading-flutter-plugin-for-jetbrains-ides-expands">Flutter plugin for JetBrains IDEs expands 🌍</h3>
<p>With the release of <strong>Flutter IntelliJ Plugin M87</strong>, you now get:</p>
<ul>
<li><p>✅ Support for Android Studio <strong>Meerkat</strong> and <strong>Narwhal</strong></p>
</li>
<li><p>✅ Installation on <strong>CLion, GoLand, PyCharm</strong>, and more JetBrains IDEs 🎉</p>
</li>
</ul>
<p>👉 To update: <strong><em>Settings</em></strong> <em>&gt;</em> <strong><em>Plugins</em></strong> <em>&gt;</em> <strong><em>Flutter</em></strong> <em>&gt;</em> <strong><em>Update</em></strong></p>
<h2 id="heading-framework"><strong>Framework</strong></h2>
<h3 id="heading-a-more-inclusive-and-accessible-flutter">🌍 A More Inclusive and Accessible Flutter</h3>
<p>Accessibility isn’t an afterthought in Flutter — it’s a <strong>core principle</strong>. With every release, the Flutter team is making sure Flutter apps feel welcoming, usable, and delightful for everyone. Flutter 3.35 brings a big set of accessibility upgrades, new tools for developers, and refinements across platforms and core widgets. Let’s dive in.</p>
<ol>
<li><p><strong>Richer Semantics on the Web</strong></p>
<p> Flutter web apps now communicate more effectively with <strong>screen readers and assistive tools</strong>. For international users, we’ve added support for <strong>semantics locales</strong>, ensuring accessibility features respect the user’s preferred language.</p>
</li>
<li><p><strong>New Tools for Accessible Widgets</strong></p>
<p> Developers now have more control when building <strong>accessible experiences</strong>:</p>
<ul>
<li><p><code>SemanticsLabelBuilder</code>: Combine multiple data points into a single, coherent announcement for screen readers without messy string concatenation.</p>
</li>
<li><p><code>SliverEnsureSemantics</code>: Wrap slivers so they’re always represented in the semantics tree — even when scrolled out of view. Perfect for complex scrollable views.</p>
</li>
</ul>
</li>
<li><p><strong>Core Widget &amp; Platform Polish</strong></p>
<p> Accessibility fixes and improvements across iOS, Android, and Flutter’s core widgets:</p>
<p> <strong>iOS</strong>:</p>
<ul>
<li><p><code>CupertinoSliverNavigationBar</code> now respects accessible text scaling.</p>
</li>
<li><p>VoiceOver tab activation behaves correctly.</p>
</li>
</ul>
</li>
</ol>
<p>    <strong>Android</strong>:</p>
<ul>
<li>TalkBack now works with platform views, fixing a key issue for apps embedding native components.</li>
</ul>
<p>    <strong>Framework-wide</strong>:</p>
<ul>
<li><p><code>CustomPainter</code> semantics properties are now backfilled, making custom-drawn UI fully accessible.</p>
</li>
<li><p>The text selection toolbar is properly aligned in <strong>right-to-left (RTL) languages</strong>, improving global support.</p>
</li>
</ul>
<h3 id="heading-material-amp-cupertino-now-have-more-power-and-more-polish">🎨 Material &amp; Cupertino now have More Power and More Polish</h3>
<p>The design libraries also got a <strong>major round of improvements</strong> — from brand new components to smoother, higher-fidelity interactions. These upgrades don’t just make your apps look and feel better; they also simplify your workflow.</p>
<p>In fact, many community packages that developers previously relied on to fill gaps in the design system are now <strong>redundant</strong>, as the core Flutter libraries provide these capabilities out of the box. Less dependency management, more time to focus on building your app.</p>
<ol>
<li><p><strong>New &amp; Enhanced Components</strong></p>
<ul>
<li><p><code>DropdownMenuFormField</code>: Easily integrate Material 3 dropdown menus into forms.</p>
</li>
<li><p><strong>Scrollable NavigationRail</strong>: Perfect for apps with many destinations.</p>
</li>
<li><p><strong>NavigationDrawer header &amp; footer</strong>: Adds layout flexibility.</p>
</li>
<li><p><code>CupertinoExpansionTile</code>: Expandable and collapsible list items with Cupertino styling.</p>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>Higher Fidelity &amp; Interactivity</strong></p>
<ul>
<li><p>Cupertino widgets now use the <strong>RSuperellipse shape</strong> for the signature iOS continuous-corner look.</p>
</li>
<li><p>Added <strong>haptic feedback</strong> to <code>CupertinoPicker</code> and <code>CupertinoSlider</code>.</p>
</li>
<li><p>Sliders can now be configured to always show the <strong>value indicator</strong>.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-more-from-the-framework">⚙️ More from the Framework</h3>
<ol>
<li><p><strong>More Powerful Slivers</strong></p>
<ul>
<li>Developers can now <strong>control the paint order (z-order)</strong> of slivers, unlocking advanced effects like overlapping sticky headers.</li>
</ul>
</li>
<li><p><strong>Navigation &amp; Forms</strong></p>
<ul>
<li><p><strong>Fullscreen dialogs</strong>: The <code>fullscreenDialog</code> property is now supported in <code>ModalRoute</code> and <code>showDialog</code>.</p>
</li>
<li><p><strong>Form reset callbacks</strong>: <code>FormField</code> now includes <code>onReset</code>, making form clearing logic easier.</p>
</li>
</ul>
</li>
<li><p><strong>Text Input &amp; Selection Refinements</strong></p>
<ul>
<li><p><strong>Unified gesture system</strong>: <code>PositionedGestureDetails</code> standardizes gesture details, making pointer-based gesture handling more generic.</p>
</li>
<li><p><strong>iOS</strong>: Single-line text fields are no longer scrollable, aligning with native behavior.</p>
</li>
<li><p><strong>Android</strong>: Added support for <strong>Home</strong> and <strong>End</strong> keys.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-looking-ahead-decoupling-design-libraries">🔮 Looking Ahead: Decoupling Design Libraries</h3>
<p>As Flutter grows, the <strong>Material</strong> and <strong>Cupertino</strong> libraries are moving toward becoming <strong>standalone packages</strong>. This means:</p>
<ul>
<li><p><strong>Faster innovation</strong>: Updates won’t be tied to Flutter’s quarterly release cycle.</p>
</li>
<li><p><strong>Community-driven contributions</strong>: More flexibility for the community to shape the design libraries.</p>
</li>
<li><p><strong>Stronger core</strong>: The Flutter framework itself becomes leaner, more stable, and more focused.</p>
</li>
</ul>
<p>For now, nothing changes in your workflow. This is the start of a long-term project, done in the open with community input.</p>
<ul>
<li><p>To read the detailed vision and rationale, see: <a target="_blank" href="https://docs.google.com/document/d/189AbzVGpxhQczTcdfJd13o_EL36t-M5jOEt1hgBIh7w/edit?usp=sharing"><strong>Decoupling Design in Flutter</strong></a><strong>.</strong></p>
</li>
<li><p>To provide feedback and help coordinate the work, please <a target="_blank" href="https://github.com/flutter/flutter/issues/101479">join the discussion on GitHub</a>.</p>
</li>
</ul>
<hr />
<p>In the next article I will address the <strong>Breaking changes and deprecations.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Flutter to Native Code Migration: Summary | 5/5]]></title><description><![CDATA[What are the highlights of this migrations?]]></description><link>https://siro.hashnode.dev/flutter-to-native-code-migration-5-5</link><guid isPermaLink="true">https://siro.hashnode.dev/flutter-to-native-code-migration-5-5</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Swift]]></category><category><![CDATA[supabase]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Tue, 22 Jul 2025 04:30:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6152c793414f78089df39fdc/c006fed0-4aad-4403-9d67-a0bab942e8c3.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a continuation of my articles on migrating from Flutter to Native code and on this particle article I summarise everyting I have covered in this series and I hope you will learn from it.</p>
<hr />
<h2>Introduction</h2>
<p>Migrating your Flutter app to <strong>native Android (Kotlin + Jetpack Compose)</strong> and <strong>iOS (Swift + SwiftUI)</strong> while using <strong>Supabase</strong> requires:</p>
<hr />
<ol>
<li><strong>Dependency Setup</strong></li>
</ol>
<ul>
<li><p><strong>Flutter:</strong> Use <code>supabase_flutter</code> (built-in client, no manual client creation).</p>
</li>
<li><p><strong>Android:</strong> Add Supabase, Ktor, Room, and Hilt dependencies in <code>build.gradle.kts</code>.</p>
</li>
<li><p><strong>iOS:</strong> Add <code>supabase-swift</code> and <code>Swinject</code> via Swift Package Manager.</p>
</li>
</ul>
<ol>
<li><strong>Access Credentials</strong></li>
</ol>
<ul>
<li><p><strong>Flutter:</strong></p>
<ul>
<li>Use <code>--dart-define</code> (secure, CI-friendly) <strong>or</strong> <code>.env</code> (dev-friendly) to inject <code>SUPABASE_URL</code> and <code>SUPABASE_ANON_KEY</code>.</li>
</ul>
</li>
<li><p><strong>Android:</strong></p>
<ul>
<li>Store credentials in <a href="http://local.properties"><code>local.properties</code></a> and expose via <code>BuildConfig</code> using <code>buildConfigField</code>.</li>
</ul>
</li>
<li><p><strong>iOS:</strong></p>
<ul>
<li>Store credentials securely in <code>Secrets.plist</code> (excluded from version control) and load at runtime.</li>
</ul>
</li>
</ul>
<ol>
<li><p><strong>Project Architecture Consistency:</strong> maintain the same layered architecture:</p>
<ul>
<li><p><strong>Core:</strong> DI, utils, configs.</p>
</li>
<li><p><strong>Data:</strong> DTOs, models, local/remote sources.</p>
</li>
<li><p><strong>Domain:</strong> Entities, repositories.</p>
</li>
<li><p><strong>Presentation:</strong> Screens/views, view models (or BLoCs), reusable components.</p>
</li>
</ul>
</li>
</ol>
<p>This keeps your mental model consistent across Flutter, Android, and iOS, easing onboarding and maintenance.</p>
<ol>
<li><p>Supabase Client Initialization</p>
<ul>
<li><p><strong>Flutter:</strong> Initialize in <code>main.dart</code> with <code>Supabase.initialize(...)</code> and access globally via <code>Supabase.instance.client</code>.</p>
</li>
<li><p><strong>Android:</strong> Manually create <code>SupabaseClient</code> in a <code>SupabaseModule</code> using Hilt, and inject it where needed.</p>
</li>
<li><p><strong>iOS:</strong> Manually create and manage <code>SupabaseClient</code> within a <code>SupabaseService</code> injected via Swinject.</p>
</li>
</ul>
</li>
<li><p>Invoking Supabase in your code</p>
<p><strong>Flutter</strong>:</p>
<pre><code class="language-dart">final result = await Supabase.instance.client.from('fruits').select();
</code></pre>
<p><strong>Android:</strong></p>
<pre><code class="language-kotlin">val result = supabase["fruits"].select().decodeList&lt;FruitDto&gt;()
</code></pre>
<p><strong>iOS:</strong></p>
<pre><code class="language-swift">let results: [FruitDTO] = try await supabase.client.from("fruits").select().execute().value
</code></pre>
</li>
<li><p><strong>Data Mapping (DTO → Entity)</strong></p>
</li>
</ol>
<ul>
<li><p><strong>Flutter:</strong> No explicit mapping needed, use <code>Fruit.fromJson(item)</code> directly.</p>
</li>
<li><p><strong>Android:</strong> Use mappers to convert <code>FruitDto</code> (network layer) to <code>Fruit</code> (domain layer) for type safety and clean separation.</p>
</li>
<li><p><strong>iOS:</strong> Use mappers to convert <code>FruitDTO</code> to <code>Fruit</code>, handling optionals and ensuring SwiftUI views get clean, non-optional data.</p>
</li>
</ul>
<p><strong>Key Benefits of This Approach:</strong></p>
<ul>
<li><p><strong>Consistency:</strong> Maintain the same structure across platforms, simplifying maintenance and future feature expansion.</p>
</li>
<li><p><strong>Testability:</strong> Clear separation of concerns with DI and explicit layers allows unit testing and easy mocking.</p>
</li>
<li><p><strong>Scalability:</strong> Supports future growth (adding new features or endpoints) without architectural rewrites.</p>
</li>
<li><p><strong>Supabase-centric simplicity:</strong> Leverage Supabase’s powerful Postgres + Auth while retaining native platform strengths.</p>
</li>
</ul>
<p><strong>Final Thought:</strong></p>
<p>Migrating a Flutter app to native platforms with Supabase is straightforward if you:</p>
<ul>
<li><p>Plan your architecture,</p>
</li>
<li><p>Handle credentials securely,</p>
</li>
<li><p>Use clear dependency management,</p>
</li>
<li><p>And maintain consistency across platforms.</p>
</li>
</ul>
<hr />
<p>This lets you fully leverage native platform capabilities while maintaining your backend could be Supabase or any other you are using, enabling you to build scalable, maintainable, and testable native apps with a clean developer experience.</p>
]]></content:encoded></item><item><title><![CDATA[Flutter to Native Code Migration: Supabase | 4/5]]></title><description><![CDATA[The Supabase Setup and Implementation]]></description><link>https://siro.hashnode.dev/flutter-to-native-code-migration-4-5</link><guid isPermaLink="true">https://siro.hashnode.dev/flutter-to-native-code-migration-4-5</guid><category><![CDATA[supabase]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Thu, 17 Jul 2025 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/IeV-VNMwdL0/upload/54623808b6434993eb740c0ecb50431c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a continuation of my articles on migrating from Flutter to Native code and on this particle article I focus solely on Supabase intergration.</p>
<hr />
<h2 id="heading-project-setup">Project Setup</h2>
<h3 id="heading-a-android">a) Android</h3>
<p>Start by creating a new Android project in Android Studio. Once your project is initialized, follow these steps to configure Supabase:</p>
<ol>
<li>In your <code>app/build.gradle.kts</code>, ensure you have:</li>
</ol>
<pre><code class="lang-kotlin">dependencies {
    <span class="hljs-comment">// Networking - Supabase &amp; Ktor</span>
    implementation(platform(libs.jan.tennert.supabase.bom))     <span class="hljs-comment">//  Supabase BOM</span>
    implementation(libs.jan.tennert.supabase.postgrest)     <span class="hljs-comment">//  Supabase PostgREST support</span>
    implementation(libs.ktor.client.android)     <span class="hljs-comment">//  Ktor HTTP client for Android</span>
    implementation(libs.kotlinx.serialization.json)     <span class="hljs-comment">//  Kotlin Serialization for Android  //  Compose JUnit4 test support</span>
}
</code></pre>
<ol start="2">
<li>Now sync your project in readiness for the next setup of your project</li>
</ol>
<h3 id="heading-b-ios">b) iOS</h3>
<p>Start by creating a new iOS project in Xcode using <strong>Swift + SwiftUI</strong>. Once your project is initialized, follow these steps to configure Supabase:</p>
<ol>
<li>Add Dependencies via the <strong>Swift Package Manager</strong> by going to:<br /> <strong>File → Add Packages</strong></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753048393987/6ac5339c-9b36-464a-bced-13ea203ae933.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p>Add Supabase by entering:</p>
<pre><code class="lang-plaintext"> https://github.com/supabase/supabase-swift.git
</code></pre>
<p> and select the latest stable version.</p>
</li>
</ol>
<h2 id="heading-access-credentials">Access Credentials</h2>
<ol>
<li><p>Go to <a target="_blank" href="https://supabase.com">Supabase</a> and signup or sign in to your account.</p>
</li>
<li><p>After creating an account you'll be prompted to create an organization.</p>
</li>
<li><p>After creating an organization you'll be prompted to create a project. You can edit the project name, add a database password and change the database region closest to you and click on <code>Create new project</code></p>
</li>
<li><p>To create the <code>fruits</code> table enter the following sql query the query editor and click on <code>Run</code></p>
<pre><code class="lang-sql"> <span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> public.fruits (
   <span class="hljs-keyword">id</span> <span class="hljs-built_in">bigint</span> <span class="hljs-keyword">generated</span> <span class="hljs-keyword">by</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">identity</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,
   title <span class="hljs-built_in">text</span> <span class="hljs-literal">null</span>,
   description <span class="hljs-built_in">character</span> <span class="hljs-built_in">varying</span> <span class="hljs-literal">null</span>,
   color <span class="hljs-built_in">text</span> <span class="hljs-literal">null</span>,
   <span class="hljs-string">"createdAt"</span> <span class="hljs-built_in">timestamp</span> <span class="hljs-keyword">with</span> <span class="hljs-built_in">time</span> zone <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">now</span>(),
   <span class="hljs-string">"updatedAt"</span> <span class="hljs-built_in">timestamp</span> <span class="hljs-keyword">with</span> <span class="hljs-built_in">time</span> zone <span class="hljs-literal">null</span>,
   <span class="hljs-keyword">constraint</span> fruits_pkey primary <span class="hljs-keyword">key</span> (<span class="hljs-keyword">id</span>)
 ) <span class="hljs-keyword">TABLESPACE</span> pg_default;
</code></pre>
</li>
<li><p>Create an RLS policy for the table so that it can be accessed in public.</p>
</li>
<li><p>Now that you have fully setup your supabase backend we need the api url and key to continue.</p>
</li>
<li><p>On the top navigation menu click on <code>Connect</code></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753043434581/5de5c360-fe5c-4749-8d8a-cd38858604f8.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>On the pop dialog that appear click on <code>App Frameworks</code></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753043834503/8b0e5770-9872-441c-9fe3-79ae9f435adc.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<hr />
<p>In my flutter app I have 2 ways of accessing the access credentials.</p>
<ol>
<li><p>Using <code>keys.json</code> + <code>String.fromEnvironment</code>.</p>
<ul>
<li>Credentials are passed at <strong>build time</strong> using <code>--dart-define</code> flags:</li>
</ul>
</li>
</ol>
<pre><code class="lang-basic">flutter <span class="hljs-keyword">run</span> --dart-define=SUPABASE_URL=https://xyz.supabase.co --dart-define=SUPABASE_ANON_KEY=abc123
</code></pre>
<ul>
<li>Load and access in <code>main.dart</code>:</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-keyword">const</span> supabaseUrl = <span class="hljs-built_in">String</span>.fromEnvironment(<span class="hljs-string">"SUPABASE_URL"</span>);
<span class="hljs-keyword">const</span> supabaseAnonKey = <span class="hljs-built_in">String</span>.fromEnvironment(<span class="hljs-string">"SUPABASE_ANON_KEY"</span>);
<span class="hljs-keyword">await</span> Supabase.initialize(url: supabaseUrl, anonKey: supabaseAnonKey);
</code></pre>
<p>Advantages:</p>
<ul>
<li><p><strong>Secure for different build environments</strong> (dev, staging, prod) without changing files.</p>
</li>
<li><p>Credentials <strong>do not live in the codebase</strong> or files that could leak into version control.</p>
</li>
<li><p>Works seamlessly with <strong>CI/CD pipelines</strong> and <strong>flavor setups</strong>.</p>
</li>
<li><p>Tree-shakable since unused constants are removed in production builds.</p>
</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li><p>Requires <strong>manual setup</strong> of <code>--dart-define</code> flags or using a tool like Flutter Flavorizr for management.</p>
</li>
<li><p>Cannot easily check or view values during development without printing them.</p>
</li>
<li><p>If you forget to pass the flags, the values will be <code>null</code> or empty at runtime.</p>
</li>
</ul>
<ol start="2">
<li><p>Using <code>.env</code> + <code>flutter_dotenv</code> package.</p>
<ul>
<li>Store credentials in a <code>.env</code> file:</li>
</ul>
</li>
</ol>
<pre><code class="lang-basic">    SUPABASE_URL=https://xyz.supabase.co
    SUPABASE_ANON_KEY=abc123
</code></pre>
<ul>
<li>Load and access in <code>main.dart</code>:</li>
</ul>
<pre><code class="lang-dart">    <span class="hljs-keyword">await</span> dotenv.load(fileName: <span class="hljs-string">".env"</span>);
    <span class="hljs-keyword">final</span> supabaseUrl = dotenv.env[<span class="hljs-string">'SUPABASE_URL'</span>];
    <span class="hljs-keyword">final</span> supabaseAnonKey = dotenv.env[<span class="hljs-string">'SUPABASE_ANON_KEY'</span>];
    <span class="hljs-keyword">await</span> Supabase.initialize(url: supabaseUrl!, anonKey: supabaseAnonKey!);
</code></pre>
<p><strong>Advantages:</strong></p>
<ul>
<li><p><strong>Simple and developer-friendly</strong> during local development.</p>
</li>
<li><p>Easy to <strong>switch environments</strong> by changing the <code>.env</code> file or using <code>.env.staging</code>, <code>.env.production</code>, etc.</p>
</li>
<li><p>No need to pass build flags manually during local testing.</p>
</li>
<li><p>Easy to view and edit credentials when debugging.</p>
</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li><p>Does not work out of the box for <strong>web builds</strong> (requires workarounds).</p>
</li>
<li><p>Credentials still live in local files, requiring careful management across environments.</p>
</li>
<li><p>Harder to handle securely in CI/CD without additional scripts for injecting environment files.</p>
</li>
</ul>
<hr />
<p>Now away from how I access my credentials in my flutter app here is how to on Android and iOS</p>
<h3 id="heading-a-android-1">a) Android</h3>
<ol>
<li><p>Copy the NEXT_PUBLIC_SUPABASE_URL &amp; NEXT_PUBLIC_SUPABASE_ANON_KEY into your <code>local.properties</code> file in your and as below:</p>
<pre><code class="lang-basic"> SUPABASE_URL=
 SUPABASE_ANON_KEY=
</code></pre>
</li>
<li><p>In your <code>app/build.gradle.kts</code> add this:</p>
</li>
</ol>
<pre><code class="lang-kotlin">
<span class="hljs-keyword">val</span> configProperties = Properties()
<span class="hljs-keyword">val</span> configFile = rootProject.file(<span class="hljs-string">"gradle/config/config.properties"</span>)
<span class="hljs-keyword">if</span> (configFile.exists()) {
    configProperties.load(configFile.inputStream())
}
</code></pre>
<p>and in your <strong>defaultConfig</strong> inside <strong>android</strong> add the <strong>properties</strong></p>
<pre><code class="lang-basic">
android {
    defaultConfig {
        <span class="hljs-keyword">val</span> properties = Properties()
        properties.<span class="hljs-keyword">load</span>(project.rootProject.file(<span class="hljs-string">"local.properties"</span>).inputStream())
        buildConfigField(<span class="hljs-string">"String"</span>, <span class="hljs-string">"SupabaseUrl"</span>, <span class="hljs-string">"\"${properties.getProperty("</span>SUPABASE_URL<span class="hljs-string">")}\""</span>)
        buildConfigField(<span class="hljs-string">"String"</span>, <span class="hljs-string">"SupabaseKey"</span>, <span class="hljs-string">"\"${properties.getProperty("</span>SUPABASE_ANON_KEY<span class="hljs-string">")}\""</span>)
    }
}
</code></pre>
<h3 id="heading-b-ios-1">b) iOS</h3>
<ol>
<li><p>Create a <code>Secrets.plist</code> (not checked into version control)</p>
</li>
<li><p>Add your access credentials as below</p>
<pre><code class="lang-xml"> <span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
 <span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">plist</span> <span class="hljs-meta-keyword">PUBLIC</span> <span class="hljs-meta-string">"-//Apple//DTD PLIST 1.0//EN"</span> <span class="hljs-meta-string">"http://www.apple.com/DTDs/PropertyList-1.0.dtd"</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">plist</span> <span class="hljs-attr">version</span>=<span class="hljs-string">"1.0"</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">dict</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>SUPABASE_URL<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>SUPABASE_ANON_KEY<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">dict</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">plist</span>&gt;</span>
</code></pre>
</li>
<li><p>Load these values into your app at runtime using:</p>
<pre><code class="lang-swift">
 <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">SupabaseSecrets</span> </span>{
     <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> url: <span class="hljs-type">String</span> = {
         <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> path = <span class="hljs-type">Bundle</span>.main.path(forResource: <span class="hljs-string">"Secrets"</span>, ofType: <span class="hljs-string">"plist"</span>),
               <span class="hljs-keyword">let</span> dict = <span class="hljs-type">NSDictionary</span>(contentsOfFile: path),
               <span class="hljs-keyword">let</span> value = dict[<span class="hljs-string">"SUPABASE_URL"</span>] <span class="hljs-keyword">as</span>? <span class="hljs-type">String</span> <span class="hljs-keyword">else</span> {
             <span class="hljs-built_in">fatalError</span>(<span class="hljs-string">"Missing SUPABASE_URL in Secrets.plist"</span>)
         }
         <span class="hljs-keyword">return</span> value
     }()

     <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> anonKey: <span class="hljs-type">String</span> = {
         <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> path = <span class="hljs-type">Bundle</span>.main.path(forResource: <span class="hljs-string">"Secrets"</span>, ofType: <span class="hljs-string">"plist"</span>),
               <span class="hljs-keyword">let</span> dict = <span class="hljs-type">NSDictionary</span>(contentsOfFile: path),
               <span class="hljs-keyword">let</span> value = dict[<span class="hljs-string">"SUPABASE_ANON_KEY"</span>] <span class="hljs-keyword">as</span>? <span class="hljs-type">String</span> <span class="hljs-keyword">else</span> {
             <span class="hljs-built_in">fatalError</span>(<span class="hljs-string">"Missing SUPABASE_ANON_KEY in Secrets.plist"</span>)
         }
         <span class="hljs-keyword">return</span> value
     }()
 }
</code></pre>
<h2 id="heading-using-the-supabase-client">Using the Supabase Client</h2>
</li>
</ol>
<p>In Flutter, the <strong>Supabase client</strong> comes built into the official <code>supabase_flutter</code> package, so you don’t need to create the client manually. Instead, you initialize it directly in the main.dart as I had already shared earlier:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">await</span> Supabase.initialize(url: supabaseUrl, anonKey: supabaseAnonKey);
</code></pre>
<p>Once initialized, you can access the client globally via:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> supabase = Supabase.instance.client;
</code></pre>
<p>However in android and ios you need to create a client manually as follows:</p>
<h3 id="heading-a-android-kotlin">a) Android (Kotlin)</h3>
<p>On Android, using the <a target="_blank" href="https://github.com/supabase-community/supabase-kt">Kotlin Supabase SDK</a>, you <strong>manually create and provide the</strong> <code>SupabaseClient</code> using dependency injection (Hilt or Koin).</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Module</span>
<span class="hljs-meta">@InstallIn(SingletonComponent::class)</span>
<span class="hljs-keyword">object</span> SupabaseModule {

    <span class="hljs-meta">@Provides</span>
    <span class="hljs-meta">@Singleton</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">provideSupabaseClient</span><span class="hljs-params">()</span></span>: SupabaseClient {
        <span class="hljs-keyword">return</span> createSupabaseClient(
            supabaseUrl = BuildConfig.SupabaseUrl,
            supabaseKey = BuildConfig.SupabaseKey
        ) {
            install(Postgrest)
        }
    }

    <span class="hljs-meta">@Provides</span>
    <span class="hljs-meta">@Singleton</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">provideSupabaseDatabase</span><span class="hljs-params">(client: <span class="hljs-type">SupabaseClient</span>)</span></span>: Postgrest {
        <span class="hljs-keyword">return</span> client.postgrest
    }
}
</code></pre>
<h3 id="heading-b-ios-swift">b) iOS (Swift)</h3>
<p>On iOS, you <strong>create the</strong> <code>SupabaseClient</code> explicitly and manage its lifecycle within a service class:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SupabaseService</span>: <span class="hljs-title">SupabaseServiceProtocol</span> </span>{
    <span class="hljs-keyword">let</span> client: <span class="hljs-type">SupabaseClient</span>

    <span class="hljs-keyword">init</span>() {
        <span class="hljs-keyword">let</span> urlString = <span class="hljs-type">SupabaseSecrets</span>.url
        <span class="hljs-keyword">let</span> anonKey = <span class="hljs-type">SupabaseSecrets</span>.anonKey

        <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> url = <span class="hljs-type">URL</span>(string: urlString), !anonKey.isEmpty <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">fatalError</span>(<span class="hljs-string">"Invalid Supabase URL or anon key. Check Secrets.plist."</span>)
        }

        <span class="hljs-keyword">self</span>.client = <span class="hljs-type">SupabaseClient</span>(
            supabaseURL: url,
            supabaseKey: anonKey
        )
    }
}
</code></pre>
<h2 id="heading-invoking-supabase-in-your-repository">Invoking Supabase in your Repository</h2>
<p>After setting up the Supabase client, the next step is using it to <strong>invoke your Supabase backend consistently across platforms</strong>. In Flutter, I just invoke the table directly:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> result = <span class="hljs-keyword">await</span> Supabase.instance.client.from(<span class="hljs-string">'fruits'</span>).select();
</code></pre>
<p>Wrapped in a <strong>repository</strong>:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FruitRepository</span> </span>{
  <span class="hljs-keyword">final</span> SupabaseClient supabase;

  FruitRepository(<span class="hljs-keyword">this</span>.supabase);

  Future&lt;<span class="hljs-built_in">List</span>&lt;Fruit&gt;&gt; fetchFruits() <span class="hljs-keyword">async</span> {
    logger(<span class="hljs-string">'Now fetching fruits'</span>);
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">final</span> result = <span class="hljs-keyword">await</span> supabase.from(<span class="hljs-string">'fruits'</span>).select();
      <span class="hljs-keyword">final</span> fruits = (result <span class="hljs-keyword">as</span> <span class="hljs-built_in">List</span>)
          .map((item) =&gt; Fruit.fromJson(item))
          .toList();
      logger(<span class="hljs-string">'<span class="hljs-subst">${fruits.length}</span> fruits fetched'</span>);
      <span class="hljs-keyword">return</span> fruits;
    } <span class="hljs-keyword">catch</span> (e) {
      logger(<span class="hljs-string">'Unable to fetch fruits: <span class="hljs-subst">$e</span>'</span>);
      <span class="hljs-keyword">return</span> [];
    }
  }
}
</code></pre>
<p>From this <strong>Direct, readable API call</strong> I can get a list of strongly typed <code>Fruit</code> objects for the UI.</p>
<h3 id="heading-a-android-kotlin-1">a) Android (Kotlin)</h3>
<p>Using the <strong>Supabase Kotlin SDK</strong>, I invoke my table with:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> result = supabase[<span class="hljs-string">"fruits"</span>].select().decodeList&lt;FruitDto&gt;()
</code></pre>
<p>Wrapped in a <strong>repository</strong> with dependency injection:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Singleton</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FruitRepository</span> <span class="hljs-meta">@Inject</span> <span class="hljs-keyword">constructor</span></span>(
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> supabase: Postgrest,
) {
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">fetchFruits</span><span class="hljs-params">()</span></span>: Flow&lt;List&lt;Fruit&gt;&gt; = flow {
        <span class="hljs-keyword">try</span> {
            Log.d(<span class="hljs-string">"TAG"</span>, <span class="hljs-string">"Now fetching fruits"</span>)
            <span class="hljs-keyword">val</span> result = supabase[<span class="hljs-string">"fruits"</span>].select().decodeList&lt;FruitDto&gt;()
            <span class="hljs-keyword">val</span> fruits = result.map { MapDtoToEntity.mapToEntity(it) }
            Log.d(<span class="hljs-string">"TAG"</span>, <span class="hljs-string">"Fetched <span class="hljs-subst">${fruits.size}</span> fruits"</span>)
            emit(fruits)
        } <span class="hljs-keyword">catch</span> (e: Exception) {
            Log.d(<span class="hljs-string">"TAG"</span>, <span class="hljs-string">"Unable to fetch fruits: <span class="hljs-subst">${e.message}</span>"</span>)
        }
    }
}
</code></pre>
<p>This request <strong>Uses Kotlin Flows</strong> for reactive streams after which it maps DTOs (Data Transfer Objects) to domain entities for clean separation.</p>
<h3 id="heading-b-ios-swift-1"><strong>b) iOS (Swift)</strong></h3>
<p>Using the <strong>Supabase Swift SDK</strong>, I invoke my table with:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> results: [<span class="hljs-type">FruitDTO</span>] = <span class="hljs-keyword">try</span> await supabase.client.from(<span class="hljs-string">"fruits"</span>).select().execute().value
</code></pre>
<p>Wrapped in a <strong>repository</strong>:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FruitRepository</span>: <span class="hljs-title">FruitRepositoryProtocol</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> supabase: <span class="hljs-type">SupabaseServiceProtocol</span>

    <span class="hljs-keyword">init</span>(supabase: <span class="hljs-type">SupabaseServiceProtocol</span>) {
        <span class="hljs-keyword">self</span>.supabase = supabase
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchFruits</span><span class="hljs-params">()</span></span> async <span class="hljs-keyword">throws</span> -&gt; [<span class="hljs-type">Fruit</span>] {
        <span class="hljs-keyword">do</span> {
            <span class="hljs-keyword">let</span> results: [<span class="hljs-type">FruitDTO</span>] = <span class="hljs-keyword">try</span> await supabase.client.from(<span class="hljs-string">"fruits"</span>).select().execute().value
            <span class="hljs-keyword">let</span> fruits = results.<span class="hljs-built_in">map</span> { <span class="hljs-type">MapDtoToEntity</span>.mapToEntity($<span class="hljs-number">0</span>) }
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"✅ Fruits fetched: \(fruits.count)"</span>)
            <span class="hljs-keyword">return</span> fruits
        } <span class="hljs-keyword">catch</span> {
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"❌ Failed to fetch fruits: \(error.localizedDescription)"</span>)
            <span class="hljs-keyword">throw</span> error
        }
    }
}
</code></pre>
<p>This request uses Swift's <strong>async/await</strong> for clean asynchronous code after which it maps DTOs to domain entities, maintaining clear architecture.</p>
<h2 id="heading-mapping-dtos-to-domain-entities">Mapping DTOs to Domain Entities</h2>
<p>Migrating my Flutter apps like SwahiLib and SongLib to native Android and iOS taught me the value of <strong>clean, explicit data handling</strong>. In Flutter, dynamic <code>fromJson</code> decoding was enough, but on Android and iOS, <strong>explicit DTO-to-entity mapping became essential</strong> for type safety, testability, and clean architecture, especially when syncing with Supabase or Core Data. This step ensured that backend changes or differences in local storage structures would not tightly couple or break my app’s business logic and UI layers.</p>
<p>Using DTOs to mirror backend data and mapping them into clean domain entities makes your code <strong>safer, clearer, and easier to maintain</strong>. It allows you to add computed properties or handle nulls gracefully while keeping your presentation and business logic clean. Whether working with Kotlin’s data classes and mappers or Swift’s Codable structs and transformation layers, this explicit mapping ensures your app can scale confidently while maintaining flexibility across Flutter, Android, and iOS environments.</p>
<p>Please take time to read more about how I did the mapping in my previous article: <a target="_blank" href="https://dev.siro.co.ke/flutter-to-native-code-migration-3-5">Flutter to Native Migration: Data Handling and Mapping</a></p>
<hr />
<p><strong>In the next article, I will talk about the highlights of this migration of Flutter to Native code</strong></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Next Article: </strong><a target="_self" href="https://dev.siro.co.ke/flutter-to-native-code-migration-5-5">Flutter to Native Code Migration: Highlights | 5/5</a></div>
</div>]]></content:encoded></item><item><title><![CDATA[Flutter to Native Code Migration: Data | 3/5]]></title><description><![CDATA[Data Handling and Mapping: Use Case being Supabase]]></description><link>https://siro.hashnode.dev/flutter-to-native-code-migration-3-5</link><guid isPermaLink="true">https://siro.hashnode.dev/flutter-to-native-code-migration-3-5</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Swift]]></category><category><![CDATA[supabase]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Thu, 19 Jun 2025 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/LqKhnDzSF-8/upload/6858e8022c58ee777ded6e8ae9d354ef.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>I learned the hard way that migrating my Flutter app to native platforms demands <strong>clean, explicit data handling</strong>. In Flutter, I relied on the dynamic <code>fromJson</code> approach, but when I migrated <strong>SwahiLib (which uses Supabase as a backend)</strong>, I realized that <strong>Android and iOS required explicit DTO-to-entity mapping for type safety, testability, and maintainability</strong>.</p>
<p>For my other app, <strong>SongLib (which uses MongoDB with a custom backend)</strong>, the Android setup worked fine with minimal mapping. However, on iOS, I still needed to map data explicitly to and from <strong>CoreDataManager</strong> whenever I wanted to sync local data, reinforcing the need for clear data handling even when using different backend services.</p>
<h2 id="heading-why-use-dtos-and-mappers">Why Use DTOs and Mappers?</h2>
<ul>
<li><p><strong>DTOs (Data Transfer Objects):</strong> Mirror the structure returned by the backend service e.g Supabase/Postgres.</p>
</li>
<li><p><strong>Domain Entities:</strong> Clean app-facing models used in your presentation/business layers.</p>
</li>
<li><p><strong>Mappers:</strong> Convert DTOs → Entities to isolate your app logic from backend changes.</p>
</li>
</ul>
<p>In <strong>Flutter</strong>, the <code>supabase_flutter</code> SDK returns data as <code>Map&lt;String, dynamic&gt;</code>, which you can directly pass into your model's <code>fromJson</code> constructor:</p>
<pre><code class="lang-dart">Fruit.fromJson(item);
</code></pre>
<p>No explicit mapping layer is needed since Dart’s dynamic typing and JSON decoding handle shape alignment directly.</p>
<p>However, in <strong>Android (Kotlin)</strong> and <strong>iOS (Swift)</strong>, it is best practice to <strong>separate the raw DTOs fetched from Supabase from your domain models</strong>. This separation ensures:</p>
<ul>
<li><p><strong>Type safety</strong> across layers.</p>
</li>
<li><p>Clean handling if the Supabase schema changes (without breaking your app immediately).</p>
</li>
<li><p>Flexibility to add computed properties or transformations in your domain models without affecting your DTOs.</p>
</li>
</ul>
<h3 id="heading-a-android-kotlin">a) Android (Kotlin)</h3>
<p><strong>FruitDTO</strong>: represents exactly what Supabase returns:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Serializable</span>
<span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FruitDto</span></span>(
    <span class="hljs-keyword">val</span> id: <span class="hljs-built_in">Int</span> = <span class="hljs-number">0</span>,
    <span class="hljs-keyword">val</span> title: String? = <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">val</span> description: String? = <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">val</span> color: String? = <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">val</span> createdAt: String? = <span class="hljs-literal">null</span>,
    <span class="hljs-keyword">val</span> updatedAt: String? = <span class="hljs-literal">null</span>,
)
</code></pre>
<p><strong>Fruit (Model)</strong>: Your clean, app-facing entity:</p>
<pre><code class="lang-dart"><span class="hljs-meta">@Keep</span>
data <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Fruit</span>(
    <span class="hljs-title">val</span> <span class="hljs-title">id</span>: <span class="hljs-title">String</span>? = <span class="hljs-title">null</span>,
    <span class="hljs-title">val</span> <span class="hljs-title">title</span>: <span class="hljs-title">String</span>? = <span class="hljs-title">null</span>,
    <span class="hljs-title">val</span> <span class="hljs-title">description</span>: <span class="hljs-title">String</span>? = <span class="hljs-title">null</span>,
    <span class="hljs-title">val</span> <span class="hljs-title">color</span>: <span class="hljs-title">String</span>? = <span class="hljs-title">null</span>,
    <span class="hljs-title">val</span> <span class="hljs-title">createdAt</span>: <span class="hljs-title">String</span>? = <span class="hljs-title">null</span>,
    <span class="hljs-title">val</span> <span class="hljs-title">updatedAt</span>: <span class="hljs-title">String</span>? = <span class="hljs-title">null</span>,
)</span>
</code></pre>
<p><strong>MapDtoToEntity (Mapper):</strong> helps to map the DTO to your domain model to keep your presentation layer and business logic decoupled from backend structure:</p>
<pre><code class="lang-kotlin">MapDtoToEntity {
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">mapToEntity</span><span class="hljs-params">(entity: <span class="hljs-type">FruitDto</span>)</span></span>: Fruit {
        <span class="hljs-keyword">return</span> Fruit(
            id = entity.id,
            title = entity.title,
            description = entity.description,
            color = entity.color,
            createdAt = entity.createdAt,
            updatedAt = entity.updatedAt,
        )
    }
}
</code></pre>
<ul>
<li><p>Ensures consistent types (<code>Int</code> id in DTO mapped to <code>String</code> id in domain if needed).</p>
</li>
<li><p>Allows DTO to evolve without tightly coupling your app logic.</p>
</li>
</ul>
<h3 id="heading-b-ios-swift">b) iOS (Swift)</h3>
<p><strong>FruitDTO</strong>: Matches the structure returned by Supabase:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">FruitDTO</span>: <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">let</span> id: <span class="hljs-type">String?</span>
    <span class="hljs-keyword">let</span> title: <span class="hljs-type">String?</span>
    <span class="hljs-keyword">let</span> description: <span class="hljs-type">String?</span>
    <span class="hljs-keyword">let</span> color: <span class="hljs-type">String?</span>
    <span class="hljs-keyword">let</span> createdAt: <span class="hljs-type">String?</span>
    <span class="hljs-keyword">let</span> updatedAt: <span class="hljs-type">String?</span>
}
</code></pre>
<p><strong>Fruit Model:</strong> Used in your SwiftUI views and logic</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Fruit</span>: <span class="hljs-title">Identifiable</span>, <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">let</span> id: <span class="hljs-type">Int</span>
    <span class="hljs-keyword">let</span> title: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> description: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> color: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> createdAt: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> updatedAt: <span class="hljs-type">String</span>
}
</code></pre>
<p><strong>MapDtoToEntity</strong> <strong>Mapper</strong>: You map from <code>FruitDTO</code> to <code>Fruit</code>, handling defaults and type transformations:</p>
<pre><code class="lang-dart">struct MapDtoToEntity {
    <span class="hljs-keyword">static</span> func mapToEntity(_ dto: FruitDTO) -&gt; Fruit {
        Fruit(
            id: Int(dto.id ?? <span class="hljs-string">"0"</span>) ?? <span class="hljs-string">""</span>,
            title: dto.title ?? <span class="hljs-string">""</span>,
            description: dto.description ?? <span class="hljs-string">""</span>,
            color: dto.color ?? <span class="hljs-string">""</span>,
            createdAt: dto.createdAt ?? <span class="hljs-string">""</span>,
            updatedAt: dto.updatedAt ?? <span class="hljs-string">""</span>
        )
    }
}
</code></pre>
<p>✅ Allows you to handle missing or optional fields gracefully.<br />✅ Keeps SwiftUI clean by providing non-optional properties for use in views.</p>
<h2 id="heading-benefits-of-explicit-mapping">Benefits of Explicit Mapping:</h2>
<ul>
<li><p>Decouples my app’s structure from the backend schema.</p>
</li>
<li><p>Handles optional/null safely.</p>
</li>
<li><p>Enables computed properties and transformation logic.</p>
</li>
<li><p>Makes your codebase scalable and testable.</p>
</li>
</ul>
<hr />
<p><strong>In the next article, I will cover Supabase setup and implementation across Android, and iOS while comparing it against my Flutter setup, including credential handling, client initialization, and practical fetching examples.</strong></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Next Article: </strong><a target="_self" href="https://dev.siro.co.ke/flutter-to-native-code-migration-4-5"><strong>Flutter to Native Code Migration: Supabase | 4/5</strong></a></div>
</div>]]></content:encoded></item><item><title><![CDATA[Flutter to Native Code Migration: Setup | 2/5]]></title><description><![CDATA[Planning Your Migration: Architecture and Project Setup]]></description><link>https://siro.hashnode.dev/flutter-to-native-code-migration-2-5</link><guid isPermaLink="true">https://siro.hashnode.dev/flutter-to-native-code-migration-2-5</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Thu, 12 Jun 2025 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/cckf4TsHAuw/upload/d077fa829b6a1161599921b88f9da226.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Migrating a Flutter app to <strong>native Android (Kotlin + Jetpack Compose)</strong> and <strong>iOS (Swift + SwiftUI)</strong> with MongoDB with custom backend as the backend can unlock native capabilities while retaining your scalable backend. Planning your architecture and project setup properly ensures a smooth transition.</p>
<hr />
<h2 id="heading-project-setup">Project Setup</h2>
<h3 id="heading-a-android">a) Android</h3>
<p>Start by creating a new Android project in Android Studio. Once your project is initialized, follow these steps to configure dependancies:</p>
<ol>
<li>In your <code>root/build.gradle.kts</code>, ensure you have:</li>
</ol>
<pre><code class="lang-kotlin">plugins {
    alias(libs.plugins.android.application) apply <span class="hljs-literal">false</span>     <span class="hljs-comment">// Android application plugin</span>
    alias(libs.plugins.kotlin.android) apply <span class="hljs-literal">false</span>      <span class="hljs-comment">// Kotlin support for Android</span>
    alias(libs.plugins.kotlin.compose) apply <span class="hljs-literal">false</span>      <span class="hljs-comment">// Jetpack Compose compiler plugin</span>
    alias(libs.plugins.dagger.hilt) apply <span class="hljs-literal">false</span>     <span class="hljs-comment">// Hilt for dependency injection</span>
    alias(libs.plugins.devtools.ksp) apply <span class="hljs-literal">false</span>     <span class="hljs-comment">// Kotlin Symbol Processing (used by Room and Hilt packages)</span>
}
</code></pre>
<ol start="2">
<li>In your <code>app/build.gradle.kts</code>, ensure you have:</li>
</ol>
<pre><code class="lang-kotlin">plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
    alias(libs.plugins.dagger.hilt)
    alias(libs.plugins.devtools.ksp)
    kotlin(<span class="hljs-string">"plugin.serialization"</span>) version <span class="hljs-string">"2.1.21"</span>
}
</code></pre>
<p>at the top and in the depencies you may want to organize them this way</p>
<pre><code class="lang-kotlin">dependencies {
    <span class="hljs-comment">// Core AndroidX &amp; Lifecycle</span>
    implementation(libs.androidx.core.ktx)      <span class="hljs-comment">// Kotlin extensions for core Android APIs</span>
    implementation(libs.androidx.lifecycle.runtime.ktx)      <span class="hljs-comment">// Lifecycle-aware components</span>
    implementation(libs.androidx.core.splashscreen)      <span class="hljs-comment">// Splash screen API</span>

    <span class="hljs-comment">// Jetpack Compose - BOM</span>
    implementation(platform(libs.androidx.compose.bom))      <span class="hljs-comment">// Compose Bill of Materials (BOM)</span>

    <span class="hljs-comment">// Jetpack Compose - Core UI</span>
    implementation(libs.androidx.activity.compose)      <span class="hljs-comment">// Activity support for Compose</span>
    implementation(libs.androidx.ui)      <span class="hljs-comment">// Core Compose UI elements</span>
    implementation(libs.androidx.ui.graphics)      <span class="hljs-comment">// Compose graphics primitives</span>

    <span class="hljs-comment">// Jetpack Compose - Material Design</span>
    implementation(libs.androidx.material3)      <span class="hljs-comment">// Material 3 UI components</span>
    implementation(libs.androidx.compose.material)      <span class="hljs-comment">// Material 2 components (legacy support)</span>
    implementation(libs.androidx.icons.extended)      <span class="hljs-comment">// Extended material icons</span>

    <span class="hljs-comment">// Jetpack Compose - Navigation &amp; State</span>
    implementation(libs.compose.navigation)      <span class="hljs-comment">// Navigation support in Compose</span>
    implementation(libs.compose.hilt.navigation)      <span class="hljs-comment">// Hilt + Navigation integration</span>
    implementation(libs.androidx.compose.livedata)      <span class="hljs-comment">// LiveData support in Compose</span>

    <span class="hljs-comment">// Jetpack Compose - Tooling &amp; Preview</span>
    implementation(libs.androidx.ui.tooling.preview)      <span class="hljs-comment">// Compose UI preview support</span>
    debugImplementation(libs.androidx.ui.tooling)      <span class="hljs-comment">// Compose UI tools (debug only)</span>
    debugImplementation(libs.androidx.ui.test.manifest)      <span class="hljs-comment">// Manifest for Compose UI tests (debug)</span>

    <span class="hljs-comment">// Navigation (Non-Compose)</span>
    implementation(libs.androidx.navigation)      <span class="hljs-comment">// AndroidX Navigation components</span>

    <span class="hljs-comment">// Room Database</span>
    implementation(libs.androidx.room.runtime)      <span class="hljs-comment">// Room runtime</span>
    ksp(libs.androidx.room.compiler)      <span class="hljs-comment">// Room annotation processor (KSP)</span>
    annotationProcessor(libs.androidx.room.compiler)      <span class="hljs-comment">// Annotation processor (for fallback/tests)</span>

    <span class="hljs-comment">// Hilt for Dependency Injection</span>
    implementation(libs.hilt.android)      <span class="hljs-comment">// Hilt core</span>
    ksp(libs.hilt.compiler)      <span class="hljs-comment">// Hilt code generation</span>
    kspAndroidTest(libs.hilt.android.compiler)      <span class="hljs-comment">// Hilt codegen for instrumentation tests</span>

    <span class="hljs-comment">// Networking (Retrofit + OkHttp)</span>
    implementation(libs.squareup.retrofit)      <span class="hljs-comment">// Retrofit for networking</span>
    implementation(libs.squareup.retrofit.gson)      <span class="hljs-comment">// Gson converter for Retrofit</span>
    implementation(libs.squareup.okhttp3.logging)      <span class="hljs-comment">// OkHttp logging interceptor</span>

    <span class="hljs-comment">// Testing - Unit Tests</span>
    testImplementation(libs.junit)      <span class="hljs-comment">// JUnit for unit testing</span>

    <span class="hljs-comment">// Testing - Android Instrumentation Tests</span>
    androidTestImplementation(libs.androidx.junit)      <span class="hljs-comment">// AndroidX JUnit extensions</span>
    androidTestImplementation(libs.androidx.espresso.core)      <span class="hljs-comment">// Espresso UI test framework</span>
    androidTestImplementation(platform(libs.androidx.compose.bom))      <span class="hljs-comment">// Compose BOM for tests</span>
    androidTestImplementation(libs.androidx.ui.test.junit4)      <span class="hljs-comment">// Compose JUnit4 test support</span>
}
</code></pre>
<ol start="3">
<li>Now sync your project in readiness for the next setup of your project</li>
</ol>
<h3 id="heading-b-ios">b) iOS</h3>
<p>Start by creating a new iOS project in Xcode using <strong>Swift + SwiftUI</strong>. Once your project is initialized, follow these steps to configure dependencies:</p>
<ol>
<li><p>Add Dependencies via the <strong>Swift Package Manager</strong> by going to:<br /> <strong>File → Add Packages</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753127425053/c03ae3de-6f9b-45d1-943a-41cb7a708692.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Add Swinject by entering:</p>
<pre><code class="lang-plaintext"> https://github.com/Swinject/Swinject.git
</code></pre>
<p> and select the latest stable version. All other packages are added that way.</p>
</li>
</ol>
<h3 id="heading-project-architecture">Project Architecture</h3>
<p>After setting up dependencies and basic wiring, it is important to have a clean architecture that can scale and remain testable across <strong>Android, and iOS</strong>.</p>
<p>I worked maintaining <strong>similar layering</strong> that was already using in <strong>Flutter</strong>:</p>
<h3 id="heading-maintaining-similar-layering-across-android-and-ios">Maintaining similar layering across Android and iOS</h3>
<ul>
<li><p><strong>Core</strong> – Dependency injection, constants, environment configurations, helpers, utilities.</p>
</li>
<li><p><strong>Data</strong> – Data models, data sources (local and remote), mappers, DTOs.</p>
</li>
<li><p><strong>Domain</strong> – Entities, repositories (interfaces and implementations).</p>
</li>
<li><p><strong>Presentation</strong> – Screens/views, view models/BLoCs, UI components, themes.</p>
</li>
</ul>
<p>This was my <strong>flutter</strong> setup</p>
<pre><code class="lang-basic">lib
├── core
│   ├── di
│       ├── injectible.dart
│   ├── utils
├── <span class="hljs-keyword">data</span>
│   ├── models
│       ├── song.dart
│   ├── sources
│       ├── local
│           ├── daos (Domain Access Objects)
│           ├── app_database.dart
│       ├── <span class="hljs-comment">remote</span>
├── domain
│   ├── repository
│       ├── song_repository.dart
├── presentation
│   ├── blocs
│       ├── selection
│       ├── home
│   ├── screens
│       ├── selection
│       ├── home
│   ├── widgets
├── app.dart (boostrapper)
└── main.dart (app launcher/initializer)
</code></pre>
<ul>
<li><p>Uses <strong>BLoC</strong> for state management.</p>
</li>
<li><p><strong>Local data</strong> (Floor) and <strong>remote data</strong> (backend client).</p>
</li>
<li><p>Dependency injection via <strong>Injectable</strong> and <strong>GetIt</strong>.</p>
</li>
<li><p>Keeps UI logic in <strong>screens</strong>, while reusable pieces of code in <strong>widgets</strong>.</p>
</li>
<li><p>Environment and constants in <strong>core/utils/env</strong>.</p>
</li>
</ul>
<h3 id="heading-a-android-kotlin-jetpack-compose">a) Android (Kotlin, Jetpack Compose)</h3>
<pre><code class="lang-basic">app
├── core
│   ├── di
│   ├── ├── AppModule.kt
│   ├── ├── NetworkModule.kt
│   ├── utils
├── <span class="hljs-keyword">data</span>
│   ├── models
│       ├── Song.kt
│   ├── samples
│   ├── sources
│       ├── local
│           ├── AppDaos.kt (<span class="hljs-keyword">Data</span> Access Objects)
│           ├── AppDatabase.kt
│       ├── <span class="hljs-comment">remote</span>
│           ├── ApiService.kt
├── domain
│   ├── entity
│       ├── UiState.kt
│   ├── repository
│       ├── SongRepository.kt
├── presentation
│   ├── components
│   ├── screens
│       ├── selection
│       ├── home
│   ├── theme
│   ├── viewmodels
│       ├── SelectionViewModel.kt
│       ├── HomeViewModel.kt
├── MainActivity.kt
└── SongLibApp.kt
</code></pre>
<ul>
<li><p>Uses <strong>ViewModel + StateFlow</strong> for state management.</p>
</li>
<li><p><strong>Dependency Injection</strong> via <strong>Koin/Hilt</strong>.</p>
</li>
<li><p>Room for local caching, Retrofit for remote.</p>
</li>
<li><p>UI uses <strong>Jetpack Compose</strong> with <strong>components</strong> for reusable UI blocks.</p>
</li>
</ul>
<h3 id="heading-b-ios-swiftui">b) iOS (SwiftUI)</h3>
<pre><code class="lang-basic">MyNewApp
├── Core
│   ├── Di
│       ├── DependencyMap.swift
│       ├── DiContainer.swift
│   ├── Utils
├── <span class="hljs-keyword">Data</span>
│   ├── Models
│       ├── Song.swift
│   ├── Samples
│   ├── Sources
│       ├── Local
│           ├── CoreDataManager.swift
│           ├── SongDataManager.swift
│           ├── MyNewApp.xcdatamodelId
│       ├── <span class="hljs-comment">Remote</span>
│           ├── ApiService.swift
│           ├── EndPoint.swift
├── Domain
│   ├── Entity
│   ├── Repository
│       ├── SongRepository.swift
├── Presentation
│   ├── Components
│   ├── Views
│       ├── Selection
│       ├── Home
│   ├── Theme
│   ├── ViewModels
│       ├── SelectionViewModel.swift
│       ├── HomeViewModel.swift
├── Assets.xcassets
├── ContentView.swift
└── SongLibApp.swift
</code></pre>
<ul>
<li><p>Uses <strong>ObservableObject + @Published</strong> or <strong>Combine</strong> for state management.</p>
</li>
<li><p>Dependency injection via lightweight <strong>DependencyMap and DiContainer</strong>.</p>
</li>
<li><p>Uses <strong>Core Data</strong> for local, <strong>ApiService.swift</strong> for remote.</p>
</li>
<li><p><strong>SwiftUI</strong> structures with <strong>Views</strong> and reusable <strong>Components</strong>.</p>
</li>
<li><p>DTO and Core Data entity mapping handled explicitly in <code>Mappers</code>.</p>
</li>
</ul>
<h3 id="heading-strengths-in-a-clean-structure">Strengths in a clean structure:</h3>
<ol>
<li><p><strong>Consistent Layering:</strong> Core, Data, Domain, Presentation clearly separate concerns hence its easy to understand and maintain.</p>
</li>
<li><p><strong>Alignment Across Platforms:</strong> The same mental model applies for Flutter (BLoC), Android (ViewModel + StateFlow), and iOS (ObservableObject/Combine). DTOs and entity mappers handled explicitly, ensuring testability and separation of network vs. app models.</p>
</li>
<li><p><strong>Testability &amp; Modularity:</strong> Dependency Injection handled cleanly (<code>Injectable</code>, <code>Koin/Hilt</code>, <code>DependencyMap/Container</code>). Local storage handled (Floor, Room, Core Data) while ApiService remains the source of truth.</p>
</li>
<li><p><strong>UI Organization:</strong> Presentation is split into screens/views, view models/BLoCs, and reusable components/widgets, keeping screens clean.</p>
</li>
<li><p><strong>Bootstrap &amp; Entry Separation:</strong> <code>main.dart</code>/<code>MyNewApp.kt</code>/<code>MyNewApp.swift</code> as app launchers. <code>app.dart</code>/<code>ContentView.swift</code> as bootstrap entry, further isolating setup.</p>
</li>
<li><p><strong>Mappers:</strong> You explicitly handle DTO ↔ Entity ↔ Local mappings in dedicated files across all platforms. This prevents leaky abstractions and future issues when the API or local storage changes.</p>
</li>
<li><p><strong>Sample Data:</strong> The <code>samples</code> folder is clean for seeding/testing, consider documenting its purpose in your repo for contributors.</p>
</li>
<li><p><strong>Future Expansion:</strong> Consider having a <code>usecases</code> subfolder inside <code>domain</code> if you add business logic later, but for a small iOS project.</p>
</li>
</ol>
<hr />
<p><strong>In the next article, we will cover Data Handling and Mapping strategies, including DTOs, entities, and why mapping layers matter when migrating.</strong></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Next Article: </strong><a target="_self" href="https://dev.siro.co.ke/flutter-to-native-code-migration-3-5"><strong>Flutter to Native Code Migration: Data | 3/5</strong></a></div>
</div>]]></content:encoded></item><item><title><![CDATA[Flutter to Native Code Migration: Why? | 1/5]]></title><description><![CDATA[Why I Rewrote My Flutter Apps in Native Kotlin and Swift]]></description><link>https://siro.hashnode.dev/flutter-to-native-code-migration-1-5</link><guid isPermaLink="true">https://siro.hashnode.dev/flutter-to-native-code-migration-1-5</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Swift]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Mon, 02 Jun 2025 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/505eectW54k/upload/fbb1915e7b131981e3f765d40f3879e2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After being a Flutter developer for years, I recently challenged myself to rewrite two of my Flutter apps into fully native Android (Kotlin) and iOS (Swift) versions using Jetpack Compose and SwiftUI respectively. I kicked things off with <a target="_blank" href="https://songlib.vercel.app">SongLib</a>, a church song library app, and midway through, I also began working on <a target="_blank" href="https://linktr.ee/swahilibapp">SwahiLib</a>, a Swahili dictionary app (Kamusi).</p>
<p>Juggling both projects taught me a lot about platform nuances, UI patterns, and managing shared logic across native ecosystems. Now, both apps are live on Google Play and the App Store! In this series of articles, I share the complete process, key insights, and what I'd approach differently to help others considering the same journey from Flutter to native development.</p>
<hr />
<h2 id="heading-introduction-why-rewrite-a-flutter-app">Introduction: Why Rewrite a Flutter App?</h2>
<ol>
<li><strong>Need for a new Challenge, after the initial Flutter success</strong></li>
</ol>
<p>When I developed SongLib and SwahiLib back in Covid time, I chose <strong>Flutter</strong> for its powerful ability to create apps for both Android and iOS from a single Dart codebase. The framework's rapid development cycle enabled efficient creation of functional apps without the need for separate platform-specific codebases. But as a person who loves new challenges (I want problems) I started thing about going native for these two apps.</p>
<ol start="2">
<li><strong>Mentorship-Driven Learning</strong></li>
</ol>
<p>As a mentor frequently helping others with mobile development, I have been regularly debugging and enhancing Android apps written in Kotlin and Java. These hands-on experiences, including reviving old Java projects and modernizing Kotlin applications, sparked curiosity about native development and revealed gaps in expertise that could limit my mentoring effectiveness.</p>
<ol start="3">
<li><strong>Professional Skill Expansion</strong></li>
</ol>
<p>My decision to rewrite my Flutter apps into native code was also driven by my desire to become a more versatile developer capable of providing comprehensive guidance across Flutter, Kotlin, and Swift. By mastering native development, I am aim to strengthen hands-on mentorship abilities and remain competitive in a field where diverse technical expertise is increasingly valuable.</p>
<ol start="4">
<li><strong>Personal Technical Challenge</strong></li>
</ol>
<p>When I started working with Flutter I was forced there by the steep learning curve that Objective C demanded if I was to develop iOS apps. I relied on my friends’ Macs to build my app for iOS which means to learn Objective C I would need to own a Mac so that I could spend quality time to learn. Over the years I was able to own a Mac on which now Xcode is installed and has been lying dormant except maybe when am building my app on iOS.</p>
<ol start="5">
<li><strong>Tapping into the rich ecosystem of Android and iOS</strong></li>
</ol>
<p>Native development unlocks access to the full spectrum of platform-specific features and optimizations that may be limited or less efficient in cross-platform frameworks. Android's ecosystem offers advanced capabilities like highly customizable push notifications with rich media and actions, robust background processing through WorkManager and foreground services, interactive home screen and lock screen widgets, and significantly smaller app sizes due to platform-optimized compilation. iOS provides sophisticated notification systems with custom UI extensions, background app refresh capabilities, Today widgets and App Clips for lightweight functionality, and efficient app thinning that delivers only necessary resources to users' devices.</p>
<p>By developing natively, apps can achieve optimal performance with smaller APK/IPA sizes, implement platform-specific user experience patterns that feel natural to users, and leverage immediate access to new OS features as they're released, all while maintaining the performance benefits of platform-optimized code compilation.</p>
<h2 id="heading-trade-offs-of-moving-from-flutter-to-native-development">Trade-offs of Moving from Flutter to Native Development</h2>
<p><strong>Advantages:</strong></p>
<ul>
<li><p><strong>Superior Performance:</strong> Direct access to platform APIs without Flutter's rendering layer, resulting in faster app startup times, smoother animations, and optimal memory management</p>
</li>
<li><p><strong>Significantly Smaller App Sizes:</strong> Native compilation produces smaller APK/IPA files compared to Flutter's bundled framework, with platform-specific optimizations like Android's ProGuard and iOS's app thinning</p>
</li>
<li><p><strong>Full Platform Feature Access:</strong> Unrestricted use of advanced capabilities like customizable push notifications, robust background processing (WorkManager/Background App Refresh), interactive widgets, and deep system integrations</p>
</li>
<li><p><strong>Authentic User Experience:</strong> Native implementation of platform-specific design patterns (Material Design for Android, Human Interface Guidelines for iOS) that feel natural and familiar to users</p>
</li>
<li><p><strong>Rich Ecosystem Access:</strong> Immediate availability of cutting-edge platform features upon OS release, extensive native libraries, and seamless integration with platform services (Google Services, Apple ecosystem)</p>
</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li><p><strong>Doubled Development Complexity:</strong> Requires learning and maintaining expertise in multiple languages (Kotlin/Java and Swift), development environments (Android Studio and Xcode), and platform-specific concepts</p>
</li>
<li><p><strong>Significant Maintenance Overhead:</strong> Maintaining separate codebases doubles development time, testing requirements, and bug fixing efforts across platforms</p>
</li>
<li><p><strong>Increased Development Costs:</strong> Need for Mac hardware for iOS development, multiple development tools, and potentially separate development teams or extended timelines</p>
</li>
<li><p><strong>Platform Fragmentation Challenges:</strong> Android's diverse device ecosystem and iOS's hardware variations require extensive testing and platform-specific optimizations</p>
</li>
<li><p><strong>Slower Feature Delivery:</strong> Implementing features twice means longer development cycles compared to Flutter's single codebase approach, potentially delaying time-to-market for new functionality</p>
</li>
</ul>
<hr />
<p>In this article I explain why I took this path as well as the trade offs that came with it just in case there is someone looking to do this one day. I next article I will be talking about the code itself from an setup perspective.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Next Article: </strong><a target="_self" href="https://dev.siro.co.ke/flutter-to-native-code-migration-2-5"><strong>Flutter to Native Code Migration: Setup | 2/5</strong></a></div>
</div>]]></content:encoded></item><item><title><![CDATA[Resurrecting an Ancient Flutter Project Without Touching the Code]]></title><description><![CDATA[How to Make the app Run Without Touching the Code]]></description><link>https://siro.hashnode.dev/resurrecting-an-ancient-flutter-project-without-touching-the-code</link><guid isPermaLink="true">https://siro.hashnode.dev/resurrecting-an-ancient-flutter-project-without-touching-the-code</guid><category><![CDATA[gradle]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Wed, 09 Apr 2025 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/wNz7_5EvUWU/upload/f8c9bfc3a87440a9d350b6273336d47e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So, my friend, you just got hired—congratulations! 🎉 You're introduced to the team, handed a laptop, given some access credentials, and then it hits you: <strong>your first task is to fix an old Flutter project that hasn’t been touched in years.</strong></p>
<p>You open the repo, clone it, and—<em>build fails</em>. Curious, you check the Git logs—<strong>the last meaningful commit was made about three years ago.</strong> Flutter has changed <em>a lot</em> since then. You’re staring at a massive codebase that still uses <strong>pre-null safety syntax</strong>, probably <strong>hardcoded dependencies</strong>, and maybe even <strong>deprecated widgets.</strong></p>
<p>It’s a mammoth project. <strong>Starting from scratch isn’t an option</strong>—it’s too big, too risky, and there’s pressure to deliver fast. And of course, you <em>did</em> say in your interview that you're <strong>"very experienced with Flutter,"</strong> right? So now your boss is expecting <strong>magic.</strong></p>
<p>But before you can even think about adding new features, you’ve got one job:</p>
<p><strong>Make. It. Run.</strong></p>
<p>This guide walks you through <strong>exactly how to do that—without touching the code.</strong></p>
<hr />
<h2 id="heading-step-1-getting-the-right-flutter-version"><strong>Step 1: Getting the Right Flutter Version</strong></h2>
<p>The first and most critical step is ensuring you're using the <strong>same Flutter version</strong> the project was built with. A lot changes in 3+ years—Dart syntax, APIs, dependency constraints—and the older the code, the more likely it is to break on a modern Flutter SDK.</p>
<p>You have <strong>two main options</strong> for setting up the correct Flutter version:</p>
<h3 id="heading-option-1-using-fvm-flutter-version-management-recommended"><strong>Option 1: Using FVM (Flutter Version Management) — Recommended</strong></h3>
<p>FVM allows you to install multiple Flutter versions and switch between them per project, without interfering with your global setup.</p>
<p>🔧 <strong>Installing FVM:</strong></p>
<pre><code class="lang-bash">dart pub global activate fvm
</code></pre>
<p>Or, if you're on macOS with Homebrew:</p>
<pre><code class="lang-bash">brew tap leoafarias/fvm  
brew install fvm
</code></pre>
<p>🔍 <strong>Identify the Required Flutter Version:</strong></p>
<ul>
<li><p>Check <code>.metadata</code> in the project root (look for <code>version: 2.0.6</code>).</p>
</li>
<li><p>If <code>.metadata</code> is missing, check <code>pubspec.lock</code> or the oldest Git commits.</p>
</li>
</ul>
<p>📥 <strong>Install &amp; Use the Correct Version:</strong></p>
<pre><code class="lang-bash">fvm install 2.0.6  <span class="hljs-comment"># Replace with your version  </span>
fvm use 2.0.6
</code></pre>
<p>🚀 <strong>Run the Project:</strong></p>
<pre><code class="lang-bash">fvm flutter pub get  
fvm flutter run
</code></pre>
<p>⚙️ <strong>IDE Setup (VS Code/Android Studio):</strong></p>
<ul>
<li><p><strong>VS Code:</strong> Install the <strong>FVM extension</strong> and select the correct version.</p>
</li>
<li><p><strong>Android Studio:</strong> Configure the Flutter SDK path to point to <code>fvm</code>’s cached version.</p>
</li>
</ul>
<h3 id="heading-option-2-manually-install-and-use-a-separate-flutter-version-no-fvm"><strong>Option 2: Manually Install and Use a Separate Flutter Version (No FVM)</strong></h3>
<p>If you prefer not to use FVM, you can manually download and switch versions.</p>
<ol>
<li><p><strong>Download the desired Flutter version</strong> from <a target="_blank" href="https://docs.flutter.dev/release/archive">Flutter SDK releases</a>.</p>
</li>
<li><p><strong>Extract it to a safe location</strong> (e.g., <code>~/flutter_versions/flutter_2_0_6/</code>).</p>
</li>
<li><p><strong>Update your PATH:</strong></p>
<p> <strong>Temporarily (for current session):</strong></p>
<pre><code class="lang-bash"> <span class="hljs-built_in">export</span> PATH=~/flutter_versions/flutter_2_0_6/bin:<span class="hljs-variable">$PATH</span>
</code></pre>
<p> <strong>Permanently (add to</strong> <code>~/.zshrc</code> or <code>~/.bashrc</code>):</p>
<pre><code class="lang-bash"> <span class="hljs-built_in">echo</span> <span class="hljs-string">'export PATH=~/flutter_versions/flutter_2_0_6/bin:$PATH'</span> &gt;&gt; ~/.zshrc  
 <span class="hljs-built_in">source</span> ~/.zshrc
</code></pre>
</li>
<li><p><strong>Verify the version:</strong></p>
<pre><code class="lang-bash"> flutter --version
</code></pre>
</li>
<li><p><strong>Run the project:</strong></p>
<pre><code class="lang-bash"> flutter pub get  
 flutter run
</code></pre>
</li>
</ol>
<hr />
<h2 id="heading-step-2-taming-the-gradle-beast"><strong>Step 2: Taming the Gradle Beast</strong></h2>
<p>If the project is 3–4 years old, the Android build system (Gradle) is likely <strong>ancient</strong> and will fight you. Here’s how to make it cooperate—<strong>without modifying the code.</strong></p>
<h3 id="heading-1-check-the-existing-gradle-setup"><strong>1. Check the Existing Gradle Setup</strong></h3>
<p>Navigate to <code>android/</code> and inspect:</p>
<ul>
<li><p><a target="_blank" href="http://gradle-wrapper.properties"><code>gradle-wrapper.properties</code></a> → Tells you the Gradle version.</p>
<pre><code class="lang-bash">  distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
</code></pre>
</li>
<li><p><code>build.gradle</code> (Project-level) → Tells you the Android Gradle Plugin (AGP) version.</p>
<pre><code class="lang-bash">  classpath <span class="hljs-string">'com.android.tools.build:gradle:4.1.0'</span>
</code></pre>
</li>
</ul>
<p>This means:</p>
<ul>
<li><p><strong>Gradle 6.7</strong></p>
</li>
<li><p><strong>AGP 4.1.0</strong></p>
</li>
</ul>
<h3 id="heading-2-let-gradle-use-its-own-version-best-for-isolation"><strong>2. Let Gradle Use Its Own Version (Best for Isolation)</strong></h3>
<p>Run from the <code>android/</code> directory:</p>
<pre><code class="lang-bash">./gradlew wrapper --gradle-version 6.7  
./gradlew build
</code></pre>
<p>This ensures the project uses its <strong>original Gradle version</strong>, even if your system has a newer one.</p>
<h3 id="heading-3-common-errors-amp-fixes"><strong>3. Common Errors &amp; Fixes</strong></h3>
<h4 id="heading-kotlin-version-incompatibility"><strong>🚨 Kotlin Version Incompatibility</strong></h4>
<p>Error:</p>
<pre><code>Kotlin version <span class="hljs-string">'1.3.50'</span> is incompatible <span class="hljs-keyword">with</span> the Kotlin Gradle plugin.
</code></pre><p>Fix:</p>
<ul>
<li><p>Check <code>android/build.gradle</code> for the Kotlin version.</p>
</li>
<li><p>Install the correct version (don’t upgrade yet).</p>
</li>
</ul>
<h4 id="heading-jdk-version-issues"><strong>🚨 JDK Version Issues</strong></h4>
<p>Gradle 6.x + AGP 4.x usually require <strong>JDK 8 or 11</strong>. If you're on JDK 17+, downgrade temporarily:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> JAVA_HOME=$(/usr/libexec/java_home -v 11)  <span class="hljs-comment"># macOS  </span>
<span class="hljs-comment"># OR  </span>
<span class="hljs-built_in">export</span> JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64  <span class="hljs-comment"># Linux</span>
</code></pre>
<h4 id="heading-androidx-migration-issues"><strong>🚨 AndroidX Migration Issues</strong></h4>
<p>If the project is <strong>pre-AndroidX</strong>, run it on an <strong>emulator/device with API ≤ 28</strong> (Android 9 or older).</p>
<h3 id="heading-pro-tip-avoid-flutter-build-initially"><strong>Pro Tip: Avoid</strong> <code>flutter build</code> Initially</h3>
<p>Start with:</p>
<pre><code class="lang-bash">flutter clean  
flutter pub get  
flutter run
</code></pre>
<p>Avoid <code>flutter build apk</code> or <code>flutter build appbundle</code>—they trigger stricter checks.</p>
<hr />
<h2 id="heading-step-3-let-android-studio-do-the-heavy-lifting"><strong>Step 3: Let Android Studio Do the Heavy Lifting</strong></h2>
<p>Even if you prefer <strong>VS Code</strong>, opening the project in <strong>Android Studio</strong> helps resolve Gradle/JDK issues automatically.</p>
<h3 id="heading-why-android-studio"><strong>Why Android Studio?</strong></h3>
<p>✅ Detects &amp; installs missing <strong>JDK versions</strong>.<br />✅ Helps with <strong>Android SDK components</strong> (e.g., legacy build tools).<br />✅ Resolves <strong>Gradle plugin compatibility</strong> issues.</p>
<h3 id="heading-what-to-do-in-android-studio"><strong>What to Do in Android Studio</strong></h3>
<ol>
<li><p><strong>Open the</strong> <code>android/</code> folder as a standalone project.</p>
</li>
<li><p><strong>Let it sync</strong>—it may prompt you to install the correct JDK.</p>
</li>
<li><p><strong>Fix JDK path</strong> (if needed):</p>
<ul>
<li><p>Go to <strong>Preferences &gt; Build, Execution, Deployment &gt; Build Tools &gt; Gradle</strong>.</p>
</li>
<li><p>Under <strong>Gradle JDK</strong>, select <strong>JDK 8 or 11</strong>.</p>
</li>
</ul>
</li>
<li><p><strong>Sync again</strong>—Gradle should now work.</p>
</li>
</ol>
<hr />
<h2 id="heading-step-4-handling-ios-if-applicable"><strong>Step 4: Handling iOS (If Applicable)</strong></h2>
<p>If the project includes iOS, you might face:</p>
<h3 id="heading-1-cocoapods-version-issues"><strong>1. CocoaPods Version Issues</strong></h3>
<p>Older Flutter projects may need <strong>CocoaPods ≤ 1.10</strong>.</p>
<pre><code class="lang-bash">sudo gem install cocoapods -v 1.10.0
</code></pre>
<h3 id="heading-2-xcode-compatibility"><strong>2. Xcode Compatibility</strong></h3>
<ul>
<li><p>Use <strong>Xcode 12-13</strong> for older projects.</p>
</li>
<li><p>Open <code>ios/Runner.xcworkspace</code> (not <code>.xcodeproj</code>).</p>
</li>
<li><p>Set the <strong>iOS Deployment Target</strong> to match <code>Podfile</code>.</p>
</li>
</ul>
<hr />
<h2 id="heading-final-checklist"><strong>Final Checklist</strong></h2>
<p>✅ <strong>Flutter version</strong> matches <code>.metadata</code>.<br />✅ <strong>Gradle &amp; AGP versions</strong> are correct.<br />✅ <strong>JDK is set to 8/11</strong> (not 17+).<br />✅ <strong>Android Studio syncs without errors</strong>.<br />✅ <strong>CocoaPods version</strong> is compatible.</p>
<hr />
<h2 id="heading-what-if-it-still-doesnt-work"><strong>What If It Still Doesn’t Work?</strong></h2>
<p>If you’ve followed all steps and still hit errors:</p>
<ol>
<li><p><strong>Check</strong> <code>pubspec.lock</code> for exact dependency versions.</p>
</li>
<li><p><strong>Run in verbose mode</strong> for detailed logs:</p>
<pre><code class="lang-bash"> flutter run -v
</code></pre>
</li>
<li><p><strong>Ask the team</strong> if they have an <strong>old setup guide</strong>.</p>
</li>
</ol>
<hr />
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>You didn’t write this code, but <strong>you can make it run</strong>. By locking the Flutter version, taming Gradle, and letting Android Studio help, you’ll <strong>resurrect the project without rewriting it</strong>.</p>
<p>Now go ahead—<strong>make that ancient Flutter app breathe again.</strong> 🚀</p>
<p>(And then, maybe, <em>slowly</em> start upgrading it…)</p>
]]></content:encoded></item><item><title><![CDATA[Make Users Smile: Fix Memory Leaks in Your Flutter App]]></title><description><![CDATA[𝗣𝗿𝗲𝘃𝗲𝗻𝘁 𝗠𝗲𝗺𝗼𝗿𝘆 𝗟𝗲𝗮𝗸𝘀 𝗶𝗻 𝗬𝗼𝘂𝗿 𝗙𝗹𝘂𝘁𝘁𝗲𝗿 𝗔𝗽𝗽𝘀! 🚨]]></description><link>https://siro.hashnode.dev/fix-memory-leaks-in-your-flutter-app</link><guid isPermaLink="true">https://siro.hashnode.dev/fix-memory-leaks-in-your-flutter-app</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Memory Leak]]></category><category><![CDATA[memory-management]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Wed, 19 Feb 2025 02:57:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739932168189/5bd6935d-18f2-4cbe-8fa9-927da098a0c0.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-ia"> </h3>
<p><strong>INTRODUCTION</strong></p>
<p>Memory leaks occur when your app retains memory that is no longer needed, leading to performance issues, crashes, and excessive resource usage.</p>
<p>I know that most of us in Flutter might have been thinking that memory leaks only impact app size, but they quietly degrade performance. Beyond consuming storage, memory leaks slow down your app, cause crashes, and harm user experience. By implementing the right strategies, you can keep your Flutter apps fast, responsive, and reliable across all platforms.</p>
<hr />
<h3 id="heading-common-causes-memory-leaks-in-flutter"><strong>Common Causes Memory Leaks in Flutter</strong></h3>
<p>Efficient memory management is crucial for maintaining a smooth and responsive Flutter app. By identifying common causes, applying platform-specific optimizations, and following best practices, you can prevent memory leaks and enhance app performance.</p>
<ol>
<li><h4 id="heading-unreleased-streams-amp-listeners"><strong>Unreleased Streams &amp; Listeners 🔌</strong></h4>
</li>
</ol>
<p>Active stream subscriptions and event listeners can persist in memory if not properly disposed of, leading to memory leaks. Always cancel stream subscriptions and remove listeners in the <code>dispose()</code> method of a <code>StatefulWidget</code>:</p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
<span class="hljs-keyword">void</span> dispose() {
  myStreamSubscription.cancel();
  myController.dispose();
  <span class="hljs-keyword">super</span>.dispose();
}
</code></pre>
<ol start="2">
<li><h4 id="heading-mismanaged-async-operations"><strong>Mismanaged Async Operations ⚡</strong></h4>
</li>
</ol>
<p>Async tasks such as HTTP requests and database queries can consume memory if not handled correctly. Use <code>CancelToken</code> to abort HTTP requests when no longer needed:</p>
<pre><code class="lang-dart">CancelToken cancelToken = CancelToken();

<span class="hljs-keyword">void</span> fetchData() {
  Dio().<span class="hljs-keyword">get</span>(<span class="hljs-string">'https://api.example.com/data'</span>, cancelToken: cancelToken);
}

<span class="hljs-meta">@override</span>
<span class="hljs-keyword">void</span> dispose() {
  cancelToken.cancel();
  <span class="hljs-keyword">super</span>.dispose();
}
</code></pre>
<p>For streams, use <code>takeWhile</code> to automatically cancel subscriptions when the widget is disposed:</p>
<pre><code class="lang-dart">stream.takeWhile((_) =&gt; mounted).listen((data) {
  <span class="hljs-comment">// Process data</span>
});
</code></pre>
<ol start="3">
<li><h4 id="heading-overuse-of-setstate"><strong>Overuse of</strong> <code>setState()</code> 🔄</h4>
</li>
</ol>
<p>Calling <code>setState()</code> excessively can cause unnecessary widget rebuilds, leading to performance issues. Optimize state updates by:</p>
<ul>
<li><p>Updating only when necessary.</p>
</li>
<li><p>Using <code>const</code> widgets to prevent unnecessary re-renders.</p>
</li>
<li><p>Leveraging state management solutions like Provider, Riverpod, or BLoC.</p>
</li>
</ul>
<ol start="4">
<li><h4 id="heading-retaining-large-data-objects"><strong>Retaining Large Data Objects 📂</strong></h4>
</li>
</ol>
<p>Large assets such as images, files, or cached data can lead to excessive memory usage if not properly managed:</p>
<ul>
<li><p><strong>Release large assets</strong> when no longer needed.</p>
</li>
<li><p><strong>Optimize images</strong> by setting <code>cacheWidth</code> and <code>cacheHeight</code> in <code>Image.asset</code> or <code>Image.file</code> to reduce memory consumption:</p>
</li>
</ul>
<pre><code class="lang-dart">Image.asset(<span class="hljs-string">'assets/image.png'</span>, cacheWidth: <span class="hljs-number">200</span>, cacheHeight: <span class="hljs-number">200</span>);
</code></pre>
<ol start="5">
<li><h4 id="heading-singletons-amp-global-variables"><strong>Singletons &amp; Global Variables 📌</strong></h4>
</li>
</ol>
<p>Singletons and global variables persist throughout the app’s lifecycle, potentially leading to memory leaks. Properly manage their lifetimes by implementing disposal mechanisms:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MySingleton</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> MySingleton _instance = MySingleton._internal();
  <span class="hljs-keyword">factory</span> MySingleton() =&gt; _instance;
  MySingleton._internal();
  <span class="hljs-keyword">void</span> dispose() {
    <span class="hljs-comment">// Cleanup resources</span>
  }
}
</code></pre>
<p>Dependency injection frameworks like <code>get_it</code> or <code>riverpod</code> can also help manage object lifetimes more effectively.</p>
<ol start="6">
<li><h4 id="heading-web-specific-memory-leaks"><strong>Web-Specific Memory Leaks 🌐</strong></h4>
</li>
</ol>
<p>Web-based Flutter applications can suffer from memory leaks if DOM elements and event listeners are not properly removed:</p>
<ul>
<li><p><strong>Dispose event listeners</strong> when widgets are destroyed.</p>
</li>
<li><p><strong>Optimize UI for web</strong>: Some mobile-specific animations may cause performance issues in browsers. Use simpler animations or conditionally disable them on web:</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-keyword">if</span> (kIsWeb) {
  <span class="hljs-comment">// Use simpler animations for web</span>
}
</code></pre>
<ol start="7">
<li><strong>Platform-Specific Optimizations 📱</strong></li>
</ol>
<ul>
<li><h4 id="heading-efficient-list-handling">Efficient List Handling</h4>
<p>  For large datasets, prefer <code>ListView.builder</code> over <code>ListView</code> to render only visible items, conserving memory:</p>
</li>
</ul>
<pre><code class="lang-dart">ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    <span class="hljs-keyword">return</span> ListTile(title: Text(items[index]));
  },
);
</code></pre>
<ul>
<li><h4 id="heading-reducing-animation-complexity">Reducing Animation Complexity</h4>
<p>  On lower-end devices or web applications, simplify complex animations to improve performance:</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-keyword">if</span> (!kIsWeb &amp;&amp; Platform.isAndroid) {
  <span class="hljs-comment">// Apply full animations</span>
} <span class="hljs-keyword">else</span> {
  <span class="hljs-comment">// Use lightweight animations for web and older devices</span>
}
</code></pre>
<hr />
<h3 id="heading-how-to-detect-memory-leaks-in-flutter"><strong>How to Detect Memory Leaks in Flutter</strong></h3>
<p>Identifying memory leaks in your Flutter app can be challenging, but with the right tools and techniques, it's achievable. Here’s how you can detect and fix memory leaks effectively:</p>
<h4 id="heading-1-use-flutter-devtools"><strong>1. Use Flutter DevTools</strong></h4>
<p>Flutter DevTools is a powerful debugging and performance monitoring tool for Dart and Flutter applications. It allows you to analyze your app’s memory usage. To access it, run:</p>
<pre><code class="lang-bash">flutter pub global run devtools
</code></pre>
<h4 id="heading-2-capture-heap-snapshots"><strong>2. Capture Heap Snapshots</strong></h4>
<p>Taking heap snapshots at different points in time helps monitor memory usage and detect leaks. These snapshots can reveal objects that aren’t being garbage collected as expected, indicating a possible memory leak.</p>
<h4 id="heading-3-review-your-code"><strong>3. Review Your Code</strong></h4>
<p>Carefully analyze your code to ensure objects are properly disposed of when no longer needed. Pay special attention to controllers, as they are a common cause of memory leaks in Flutter. Always dispose of them correctly to prevent unnecessary memory retention.</p>
<p>By leveraging these techniques, you can keep your Flutter app efficient and free of memory leaks.</p>
<hr />
<h3 id="heading-best-practices-amp-tools"><strong>Best Practices &amp; Tools</strong></h3>
<ol>
<li><p><strong>Always override</strong> <code>dispose()</code> in <code>StatefulWidget</code> to clean up controllers, streams, and listeners.</p>
</li>
<li><p><strong>Use Flutter DevTools</strong> to monitor memory and detect leaks early.</p>
</li>
<li><p><strong>Manage object retention</strong>: Avoid caching objects indefinitely; use weak references where applicable.</p>
</li>
<li><p><strong>Optimize async execution</strong>: Properly handle <code>async</code>/<code>await</code> and ensure tasks are canceled when no longer needed.</p>
</li>
</ol>
<hr />
<p><strong>Let’s create Flutter apps that are not just beautiful but also high-performing! 🚀💡</strong></p>
]]></content:encoded></item><item><title><![CDATA[Make Users Smile: Optimize Your Flutter App's Performance]]></title><description><![CDATA[A Deep Dive into Strategies You Can Implement]]></description><link>https://siro.hashnode.dev/optimize-your-flutter-apps-performance</link><guid isPermaLink="true">https://siro.hashnode.dev/optimize-your-flutter-apps-performance</guid><category><![CDATA[optimization]]></category><category><![CDATA[airbnb]]></category><category><![CDATA[Flutter]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Mon, 06 Jan 2025 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739293527344/7239f5d3-0a69-4069-a1d9-ecf942ba8868.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">INTRODUCTION</h2>
<p>Performance optimization isn’t just a nice-to-have—it’s a necessity for large scale mobile apps like Airbnb that cater to millions of users across diverse devices. Slow apps frustrate users, increase uninstall rates, and harm brand reputation. Before we dive in into how we can achieve optimization for our Flutter apps we will first look at why we need the optimization to begin with.</p>
<h3 id="heading-why-performance-optimization-matters">Why Performance Optimization Matters</h3>
<p>1. <strong>User Retention &amp; Engagement</strong></p>
<ul>
<li><p><strong>Fact</strong>: 70% of users abandon apps that take more than <strong>3 seconds</strong> to load.</p>
</li>
<li><p><strong>Impact</strong>: Slow apps frustrate users, leading to uninstalls. For example, <strong>Pinterest improved perceived load times by 40%</strong>, boosting user engagement by <strong>15%</strong>.</p>
</li>
</ul>
<p>2. <strong>Global Market Penetration</strong></p>
<ul>
<li><p><strong>Emerging Markets</strong>: Over 60% of smartphone users in regions like India and Africa use <strong>low-RAM devices</strong> (&lt;4GB). A laggy app alienates this massive audience.</p>
</li>
<li><p><strong>Case Study</strong>: <em>Lite apps</em> have been built specifically for low-end devices, resulting in a <strong>30% increase</strong> in installations in emerging markets.</p>
</li>
</ul>
<p>3. <strong>Conversion Rates &amp; Revenue</strong></p>
<ul>
<li><p><strong>E-commerce Impact</strong>: Amazon found that <strong>every 100ms delay</strong> in load time cost them <strong>1% in sales</strong>.</p>
</li>
<li><p><strong>App Example</strong>: <strong>Walmart saw a 2% increase in conversions</strong> for every <strong>1-second improvement</strong> in load time.</p>
</li>
</ul>
<p>4. <strong>Battery Efficiency</strong></p>
<ul>
<li><p><strong>User Loyalty</strong>: Apps draining battery life are uninstalled <strong>2.5x faster</strong>.</p>
</li>
<li><p><strong>Technical Angle</strong>: Heavy animations and background tasks force CPUs/GPUs to work harder, shortening battery life.</p>
</li>
</ul>
<p>5. <strong>Search Engine Rankings (SEO)</strong></p>
<ul>
<li><p><strong>Web Apps</strong>: Google’s <strong>Core Web Vitals</strong> metrics (e.g., Largest Contentful Paint) directly affect SEO rankings. Slow Flutter web apps lose visibility.</p>
</li>
<li><p><strong>Data</strong>: Pages meeting Core Web Vitals thresholds have a <strong>24% lower bounce rate</strong>.</p>
</li>
</ul>
<p>6. <strong>Competitive Differentiation</strong></p>
<ul>
<li><p><strong>Stand Out</strong>: Apps like <strong>Instagram</strong> and <strong>Airbnb</strong> prioritize performance to stay ahead. A smooth app can be your <strong>unique selling point (USP)</strong> in crowded markets.</p>
</li>
<li><p><strong>Example</strong>: <strong>TikTok’s</strong> success is partly due to its seamless scroll and load times, even on low-end devices.</p>
</li>
</ul>
<p>7. <strong>Reduced Support Costs</strong></p>
<ul>
<li><p><strong>Hidden Savings</strong>: Poor performance leads to <strong>5x more support tickets</strong> for crashes and lag. Optimizing reduces maintenance overhead.</p>
</li>
<li><p><strong>Netflix Example</strong>: By optimizing startup time, Netflix cut support queries by <strong>20%</strong>.</p>
</li>
</ul>
<p>8. <strong>Accessibility &amp; Inclusivity</strong></p>
<ul>
<li><p><strong>Broader Reach</strong>: Slow apps exclude users with older devices or disabilities.</p>
</li>
<li><p><strong>WCAG Compliance</strong>: Performance optimizations (e.g., reduced animations) align with <strong>Web Content Accessibility Guidelines (WCAG)</strong>.</p>
</li>
</ul>
<p>9. <strong>App Store Rankings</strong></p>
<ul>
<li><p><strong>Algorithm Boost</strong>: Both Google Play and Apple’s App Store prioritize apps with <strong>lower crash rates</strong> and <strong>faster load times</strong>.</p>
</li>
<li><p><strong>Data</strong>: Apps with a 5-star rating (often tied to performance) get <strong>4.6x more downloads</strong> than 1-star apps.</p>
</li>
</ul>
<p>10. <strong>Environmental Impact</strong></p>
<ul>
<li><strong>Sustainability</strong>: Efficient code reduces energy consumption. For example, <strong>Google reduced YouTube’s carbon footprint by 50%</strong> via performance tweaks.</li>
</ul>
<hr />
<h2 id="heading-techniques-you-apply-to-achieve-optimization">Techniques You Apply to Achieve Optimization</h2>
<p>The Hidden Cost of Ignoring Performance reveals concerning statistics: 88% of users are less likely to return to a site or app after a bad experience according to Adobe, and a user who uninstalls your app costs 5x more to reacquire than to retain. While Airbnb achieves smooth performance natively, you can accomplish the same in Flutter. In this article, I'll dissect how Airbnb ensures smooth performance in their apps and translate those strategies into actionable steps for Flutter developers. Let's dive in!</p>
<h3 id="heading-1-detect-performance-bottlenecks-proactively">1. Detect Performance Bottlenecks Proactively</h3>
<p><strong>Monitor Frame Rates with</strong> <code>SchedulerBinding</code><strong>.</strong></p>
<p>Airbnb uses real-time metrics to detect lag. In Flutter, you can track frame rendering times to dynamically adjust UI complexity:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">void</span> checkPerformance() {
  SchedulerBinding.instance.addPostFrameCallback((_) {
    <span class="hljs-keyword">final</span> frameTime = SchedulerBinding.instance.drawFrameDuration;
    <span class="hljs-keyword">if</span> (frameTime &gt; <span class="hljs-keyword">const</span> <span class="hljs-built_in">Duration</span>(milliseconds: <span class="hljs-number">16</span>)) { <span class="hljs-comment">// 60 FPS threshold</span>
      setState(() =&gt; isLowPerformance = <span class="hljs-keyword">true</span>);
    }
  });
}
</code></pre>
<p><strong>Why this works</strong>: Flutter targets 60 FPS (16ms per frame). If rendering exceeds this, simplify the UI.</p>
<p><strong>Tools to Profile Performance</strong></p>
<ul>
<li><p><strong>Flutter DevTools</strong>: Identify junk with the Performance Overlay (<code>flutter run --profile</code>).</p>
</li>
<li><p><strong>Firebase Performance Monitoring</strong>: Track metrics like startup time and frame rates in production.</p>
</li>
</ul>
<h3 id="heading-2-optimize-animations-for-low-end-devices">2. Optimize Animations for Low-End Devices</h3>
<p><strong>Dynamically Disable Animations</strong></p>
<p>Use <code>TickerMode</code> to toggle animations based on device health:</p>
<pre><code class="lang-dart">TickerMode(
  enabled: !isLowPerformance,
  child: SlideTransition(
    position: _animationController.drive(CurveTween(curve: Curves.easeOut)),
  ),
);
</code></pre>
<p><strong>Alternative Strategies</strong></p>
<ul>
<li><p><strong>Simplify Animations</strong>: Replace complex Lottie animations with static SVGs.</p>
</li>
<li><p><strong>Lower Frame Rates</strong>: Reduce <code>AnimationController</code> duration for low-end devices:</p>
</li>
</ul>
<pre><code class="lang-dart">_animationController.duration = isLowPerformance 
    ? <span class="hljs-keyword">const</span> <span class="hljs-built_in">Duration</span>(milliseconds: <span class="hljs-number">300</span>) 
    : <span class="hljs-keyword">const</span> <span class="hljs-built_in">Duration</span>(milliseconds: <span class="hljs-number">500</span>);
</code></pre>
<h3 id="heading-3-optimize-resource-management">3. Optimize Resource Management</h3>
<p><strong>Efficient List Rendering</strong></p>
<p><code>ListView.builder</code> is your friend, but go further:</p>
<ul>
<li><p><strong>Use</strong> <code>itemExtent</code>: Fix item height to avoid expensive layout recalculations.</p>
</li>
<li><p><strong>Avoid</strong> <code>shrinkWrap</code>: It forces widgets to compute their size upfront, causing delays.</p>
</li>
</ul>
<p><strong>Image Optimization</strong></p>
<ul>
<li><strong>Lazy Loading</strong>: Use <code>cached_network_image</code> with placeholders:</li>
</ul>
<pre><code class="lang-dart">CachedNetworkImage(
  imageUrl: <span class="hljs-string">"https://example.com/image.jpg"</span>,
  placeholder: (context, url) =&gt; Container(color: Colors.grey[<span class="hljs-number">300</span>]),
);
</code></pre>
<ul>
<li><strong>Resize Images</strong>: Serve lower-resolution images for slow devices using URL parameters (e.g., <code>image.jpg?width=400</code>).</li>
</ul>
<h3 id="heading-4-offload-heavy-tasks-with-isolates">4. Offload Heavy Tasks with Isolates</h3>
<p><strong>Prevent UI Freezes</strong></p>
<p>Long-running tasks (e.g., data parsing, complex calculations) should run in isolates:</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; processData() <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> receivePort = ReceivePort();
  <span class="hljs-keyword">await</span> Isolate.spawn(_heavyTask, receivePort.sendPort);
}

<span class="hljs-keyword">void</span> _heavyTask(SendPort sendPort) {
  <span class="hljs-comment">// Your compute-heavy logic here</span>
  sendPort.send(result);
}
</code></pre>
<p><strong>When to Use</strong> <code>Isolate</code> <strong>vs</strong> <code>compute()</code></p>
<ul>
<li><p><code>compute()</code>: Short-lived tasks (&lt; 5 seconds). Simple syntax.</p>
</li>
<li><p><code>Isolate</code>: Long-running tasks. More control (e.g., two-way communication).</p>
</li>
</ul>
<hr />
<h3 id="heading-5-advanced-optimization-techniques-airbnbs-secret-sauce">5. Advanced Optimization Techniques (Airbnb’s Secret Sauce)</h3>
<h3 id="heading-a-shader-precompilation">A. Shader Precompilation</h3>
<p>Avoid first-run junk by pre-warming shaders during app launch:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">void</span> warmUpShaders() {
  WidgetsFlutterBinding.ensureInitialized();
  <span class="hljs-keyword">final</span> FlutterView view = WidgetsBinding.instance.platformDispatcher.views.first;
  <span class="hljs-keyword">await</span> FragmentProgram.precompile(view.context, assetKey: <span class="hljs-string">'shaders/my_shader.frag'</span>);
}
</code></pre>
<h3 id="heading-b-state-management-tuning">B. State Management Tuning</h3>
<ul>
<li><p>Use <code>const</code> widgets to minimize rebuilds.</p>
</li>
<li><p>Adopt <code>Provider</code> or <code>Riverpod</code> for granular state updates.</p>
</li>
</ul>
<h3 id="heading-c-network-optimization">C. Network Optimization</h3>
<ul>
<li><p><strong>Batch API Requests</strong>: Reduce round trips for data-heavy screens.</p>
</li>
<li><p><strong>Cache Strategically</strong>: Use <code>dio</code> or <code>http</code> with <code>shared_preferences</code> for offline support.</p>
</li>
</ul>
<h3 id="heading-6-test-like-airbnb-real-world-validation">6. Test Like Airbnb: Real-World Validation</h3>
<ul>
<li><p><strong>Device Lab Testing</strong>: Run on low-end devices (e.g., Android Go Edition).</p>
</li>
<li><p><strong>User Feedback Loops</strong>: Monitor app store reviews for performance complaints.</p>
</li>
<li><p><strong>Automate Profiling</strong>: Integrate performance checks into CI/CD pipelines.</p>
</li>
</ul>
<hr />
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ol>
<li><p><strong>Monitor Early</strong>: Use <code>SchedulerBinding</code> to detect lag before users do.</p>
</li>
<li><p><strong>Adapt Dynamically</strong>: Simplify UI/animations for low-end devices.</p>
</li>
<li><p><strong>Isolate Wisely</strong>: Keep the UI thread free for rendering.</p>
</li>
<li><p><strong>Test Relentlessly</strong>: Performance is a feature—build it into your workflow.</p>
</li>
</ol>
<hr />
<p>By adopting these strategies, you’re not just coding—you’re engineering resilient, user-first apps. As Airbnb proves, performance optimization isn’t magic; it’s a mindset. Ready to make your Flutter app bulletproof? � Start optimizing today!</p>
<p><strong>Further Reading</strong>:</p>
<ul>
<li><p><a target="_blank" href="https://docs.flutter.dev/perf/best-practices">Flutter Performance Best Practices</a></p>
</li>
<li><p><a target="_blank" href="https://medium.com/airbnb-engineering">Airbnb Engineering Blog</a> (for native optimization insights)</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Ditching Poor Flutter Habits]]></title><description><![CDATA[In Flutter development, there a few challenges that frequently surface: ensuring responsive design across devices and effectively managing resource…]]></description><link>https://siro.hashnode.dev/ditching-poor-flutter-habits</link><guid isPermaLink="true">https://siro.hashnode.dev/ditching-poor-flutter-habits</guid><category><![CDATA[Disposer]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[#mediaquery]]></category><category><![CDATA[LayoutBuilder]]></category><category><![CDATA[StatefulWidget]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Tue, 19 Nov 2024 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733205675925/dbaff27d-7cba-41bc-84ea-522d50d4c8fd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>In Flutter development, there a few challenges that frequently surface: ensuring responsive design across devices and effectively managing resource disposal. Beginners often fall into common traps, such as over-relying on <code>MediaQuery</code> for responsive layouts or defaulting to <code>StatefulWidget</code> for resource management. But what if I told you there are better, smarter ways to handle these issues?</p>
<ol>
<li><h3 id="heading-say-goodbye-to-mediaquery-for-sizing">Say Goodbye to MediaQuery for Sizing</h3>
</li>
</ol>
<h4 id="heading-the-problem-with-fractional-sizing"><strong>The Problem with Fractional Sizing</strong></h4>
<p>At first glance, <code>MediaQuery</code> seems like a convenient way to scale widgets based on screen size. For instance:</p>
<pre><code class="lang-dart">Container(
  width: MediaQuery.of(context).size.width * <span class="hljs-number">0.3</span>,
  child: Text(<span class="hljs-string">'Click Me!'</span>),
)
</code></pre>
<p>At first, this seems practical — the container adapts to screen size. However, this approach comes with significant drawbacks:</p>
<ul>
<li><p><strong>Rigid Layouts</strong>: Simply scaling widgets often results in uninspired designs that fail to adapt meaningfully to different devices.</p>
</li>
<li><p><strong>Missed Opportunities</strong>: Larger screens can display additional content or reorganize layouts for better usability. Fractional sizing just makes things bigger or smaller, missing the chance to optimize layouts.</p>
</li>
</ul>
<h4 id="heading-a-better-approach-constraints-and-adaptable-layouts"><strong>A Better Approach: Constraints and Adaptable Layouts</strong></h4>
<p>Flutter provides layout widgets like <code>Flexible</code>, <code>Expanded</code>, and <code>LayoutBuilder</code> to create designs that adapt dynamically to available space. For example:</p>
<pre><code class="lang-dart">Row(
  children: [
    Flexible(
      child: Container(color: Colors.blue),
    ),
    Flexible(
      child: Container(color: Colors.red),
    ),
  ],
)
</code></pre>
<p>In this example, the two containers share space proportionally, adapting naturally to different screen sizes.</p>
<p>For more complex scenarios, <code>LayoutBuilder</code> gives you control over how your UI changes based on constraints:</p>
<pre><code class="lang-dart">Widget responsiveLayout(BuildContext context) {
  <span class="hljs-keyword">return</span> LayoutBuilder(
    builder: (context, constraints) {
      <span class="hljs-keyword">if</span> (constraints.maxWidth &gt; <span class="hljs-number">600</span>) {
        <span class="hljs-keyword">return</span> Row(
          children: [
            Expanded(child: Text(<span class="hljs-string">'Sidebar'</span>)),
            Expanded(child: Text(<span class="hljs-string">'Main Content'</span>)),
          ],
        );
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> Column(
          children: [
            Text(<span class="hljs-string">'Main Content'</span>),
            Text(<span class="hljs-string">'Sidebar'</span>),
          ],
        );
      }
    },
  );
}
</code></pre>
<p>This code dynamically adjusts the layout based on available width, creating a truly responsive experience.</p>
<h4 id="heading-responsiveness-beyond-scaling"><strong>Responsiveness Beyond Scaling</strong></h4>
<p>Responsive design isn’t just about resizing widgets; it’s about rethinking the user experience for each device. For example:</p>
<ul>
<li><p>On a phone: Use compact designs and stacked layouts.</p>
</li>
<li><p>On a tablet: Leverage the extra space for side-by-side views.</p>
</li>
<li><p>On a desktop: Add more content or features to take advantage of the larger screen.</p>
</li>
</ul>
<ol start="2">
<li><h3 id="heading-simplifying-resource-management-with-disposer">Simplifying Resource Management with <code>Disposer</code></h3>
</li>
</ol>
<h4 id="heading-the-challenges-of-statefulwidget"><strong>The Challenges of</strong> <code>StatefulWidget</code></h4>
<p>When working with resources like <code>TextEditingController</code> or <code>FocusNode</code>, <code>StatefulWidget</code> is often used to ensure proper disposal:</p>
<pre><code class="lang-dart">dartCopy codeclass MyStatefulWidget <span class="hljs-keyword">extends</span> StatefulWidget {
  <span class="hljs-meta">@override</span>
  _MyStatefulWidgetState createState() =&gt; _MyStatefulWidgetState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MyStatefulWidgetState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">MyStatefulWidget</span>&gt; </span>{
  <span class="hljs-keyword">final</span> TextEditingController _controller = TextEditingController();
  <span class="hljs-keyword">final</span> FocusNode _focusNode = FocusNode();

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

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> TextField(
      controller: _controller,
      focusNode: _focusNode,
    );
  }
}
</code></pre>
<p>While this works, <code>StatefulWidget</code> can be verbose, less readable, and harder to manage for complex apps. It also violates principles like <strong>Single Responsibility</strong> and <strong>Separation of Concerns</strong>, as it combines UI-building logic with state management.</p>
<h4 id="heading-a-smarter-solution-the-disposer-widget"><strong>A Smarter Solution: The</strong> <code>Disposer</code> Widget</h4>
<p>The <code>Disposer</code> widget allows you to use <code>StatelessWidget</code> while still properly disposing of resources. Here’s how it works:</p>
<ol>
<li><p><strong>Create the</strong> <code>Disposer</code> Widget</p>
<pre><code class="lang-dart"> Disposer <span class="hljs-keyword">extends</span> StatefulWidget {
   <span class="hljs-keyword">final</span> VoidCallback dispose;

   <span class="hljs-keyword">const</span> Disposer({<span class="hljs-keyword">super</span>.key, <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.dispose});

   <span class="hljs-meta">@override</span>
   _DisposerState createState() =&gt; _DisposerState();
 }

 <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_DisposerState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">Disposer</span>&gt; </span>{
   <span class="hljs-meta">@override</span>
   Widget build(BuildContext context) =&gt; <span class="hljs-keyword">const</span> SizedBox.shrink();

   <span class="hljs-meta">@override</span>
   <span class="hljs-keyword">void</span> dispose() {
     widget.dispose();
     <span class="hljs-keyword">super</span>.dispose();
   }
 }
</code></pre>
</li>
<li><p><strong>Use It in a</strong> <code>StatelessWidget</code></p>
<pre><code class="lang-dart"> DisposerExampleView <span class="hljs-keyword">extends</span> StatelessWidget {
   <span class="hljs-keyword">final</span> TextEditingController controller = TextEditingController();
   <span class="hljs-keyword">final</span> FocusNode focusNode = FocusNode();

   <span class="hljs-keyword">void</span> dispose() {
     controller.dispose();
     focusNode.dispose();
   }

   <span class="hljs-meta">@override</span>
   Widget build(BuildContext context) {
     <span class="hljs-keyword">return</span> Scaffold(
       appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Disposer Example'</span>)),
       body: Column(
         children: [
           TextField(
             controller: controller,
             focusNode: focusNode,
           ),
           Disposer(dispose: dispose),
         ],
       ),
     );
   }
 }
</code></pre>
</li>
</ol>
<p>This setup ensures proper cleanup while keeping your code clean and readable.</p>
<h4 id="heading-benefits-of-using-disposer"><strong>Benefits of Using</strong> <code>Disposer</code></h4>
<ul>
<li><p><strong>Shorter Code</strong>: Eliminate the need for boilerplate-heavy <code>StatefulWidget</code>.</p>
</li>
<li><p><strong>Improved Readability</strong>: Focus on UI and logic without intertwining state management.</p>
</li>
<li><p><strong>Reusable Logic</strong>: The <code>Disposer</code> widget can handle any resource with a <code>dispose</code> method, such as animation controllers or streams.</p>
</li>
</ul>
<h3 id="heading-bringing-it-all-together">Bringing It All Together</h3>
<p>By combining constraint-based layouts with the <code>Disposer</code> widget, you can create Flutter apps that are not only more responsive but also cleaner and easier to maintain. Here’s a practical example:</p>
<pre><code class="lang-dart">ResponsiveAndCleanApp <span class="hljs-keyword">extends</span> StatelessWidget {
  <span class="hljs-keyword">final</span> TextEditingController controller = TextEditingController();
  <span class="hljs-keyword">final</span> FocusNode focusNode = FocusNode();

  <span class="hljs-keyword">void</span> dispose() {
    controller.dispose();
    focusNode.dispose();
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> LayoutBuilder(
      builder: (context, constraints) {
        <span class="hljs-keyword">return</span> Scaffold(
          appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Smarter Flutter App'</span>)),
          body: constraints.maxWidth &gt; <span class="hljs-number">600</span>
              ? Row(
                  children: [
                    Expanded(child: TextField(controller: controller)),
                    Expanded(child: Text(<span class="hljs-string">'Tablet Layout'</span>)),
                  ],
                )
              : Column(
                  children: [
                    TextField(controller: controller),
                    Text(<span class="hljs-string">'Mobile Layout'</span>),
                  ],
                ),
        );
      },
    );
  }
}
</code></pre>
<p>This app adjusts its layout based on screen size while using the <code>Disposer</code> widget for resource management.</p>
<hr />
<ol start="3">
<li><h3 id="heading-avoid-embedding-too-much-logic-in-widgets-and-overloading-the-build-method">Avoid Embedding Too Much Logic in Widgets and Overloading the Build Method</h3>
</li>
</ol>
<p>One of the most common pitfalls in Flutter development is overloading the <code>build</code> method with too much logic or embedding non-UI-related logic directly in widgets. This approach can make the code harder to read, maintain, and debug while also leading to unnecessary widget rebuilds and performance issues.</p>
<h4 id="heading-why-its-bad">Why It's Bad:</h4>
<ol>
<li><p><strong>Violates Separation of Concerns</strong>:</p>
<ul>
<li><p>Widgets are primarily responsible for building the UI, not managing business logic.</p>
</li>
<li><p>Mixing logic and UI tightly couples the two, making it difficult to refactor or test.</p>
</li>
</ul>
</li>
<li><p><strong>Decreases Readability</strong>:</p>
<ul>
<li>Overloaded <code>build</code> methods become cluttered with conditional logic, loops, and state handling, making it harder for others (or your future self) to understand the code.</li>
</ul>
</li>
<li><p><strong>Causes Unnecessary Rebuilds</strong>:</p>
<ul>
<li>When a widget rebuilds, all embedded logic gets reevaluated, even if it doesn't need to change.</li>
</ul>
</li>
</ol>
<h4 id="heading-what-to-do-instead">What to Do Instead:</h4>
<ol>
<li><p><strong>Separate Business Logic</strong>:</p>
<ul>
<li><p>Extract non-UI-related logic into dedicated classes, controllers, or state management solutions such as <code>Provider</code>, <code>Bloc</code>, or <code>Riverpod</code>.</p>
</li>
<li><p>Example: Instead of fetching data directly in the widget, use a state management approach to handle the logic and pass the state to the widget.</p>
</li>
</ul>
</li>
<li><p><strong>Break Down Widgets</strong>:</p>
<ul>
<li><p>Divide complex widgets into smaller, reusable components. This not only makes the codebase cleaner but also limits the scope of rebuilds.</p>
</li>
<li><p>Example: If a widget has a list and a button, separate the list into its own widget and the button into another.</p>
</li>
</ul>
</li>
<li><p><strong>Use Stateless Logic Helpers</strong>:</p>
<ul>
<li>For reusable logic or calculations, create helper functions or utility classes that can be called without being tied to the widget lifecycle.</li>
</ul>
</li>
<li><p><strong>Memoize Computations</strong>:</p>
<ul>
<li>If the logic in the <code>build</code> method involves expensive calculations, consider memoizing results using techniques like <code>ValueListenableBuilder</code> or <code>Selector</code> to avoid unnecessary recomputation.</li>
</ul>
</li>
</ol>
<h4 id="heading-example-from-bad-to-better">Example: From Bad to Better</h4>
<p><strong>Bad Practice (Overloaded Build Method):</strong></p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductList</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-comment">// Fetching data directly in the build method</span>
    <span class="hljs-keyword">final</span> products = fetchProductsFromApi();

    <span class="hljs-keyword">return</span> ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        <span class="hljs-comment">// Business logic embedded in UI</span>
        <span class="hljs-keyword">final</span> discountedPrice = products[index].price * <span class="hljs-number">0.9</span>;
        <span class="hljs-keyword">return</span> ListTile(
          title: Text(products[index].name),
          subtitle: Text(<span class="hljs-string">'Price: \$<span class="hljs-subst">${discountedPrice.toStringAsFixed(<span class="hljs-number">2</span>)}</span>'</span>),
        );
      },
    );
  }
}
</code></pre>
<p><strong>Better Practice (Separated Logic):</strong></p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductList</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-comment">// Pass data/state via Provider or another state management solution</span>
    <span class="hljs-keyword">final</span> products = context.watch&lt;ProductProvider&gt;().products;

    <span class="hljs-keyword">return</span> ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        <span class="hljs-keyword">return</span> ProductTile(product: products[index]);
      },
    );
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductTile</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> Product product;

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

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> discountedPrice = product.price * <span class="hljs-number">0.9</span>;
    <span class="hljs-keyword">return</span> ListTile(
      title: Text(product.name),
      subtitle: Text(<span class="hljs-string">'Price: \$<span class="hljs-subst">${discountedPrice.toStringAsFixed(<span class="hljs-number">2</span>)}</span>'</span>),
    );
  }
}
</code></pre>
<h4 id="heading-benefits">Benefits:</h4>
<ul>
<li><p>The <code>ProductList</code> widget now only manages the layout and delegates business logic to the <code>ProductProvider</code> and <code>ProductTile</code>.</p>
</li>
<li><p>The code is cleaner, reusable, and easier to test.</p>
</li>
<li><p>Changes to business logic (e.g., how discounts are calculated) can be made without touching the UI code.</p>
</li>
</ul>
<p>By avoiding overloaded <code>build</code> methods and separating logic, your Flutter code will be more efficient, maintainable, and easier to debug.</p>
<h3 id="heading-other-common-poor-habits">Other Common Poor Habits</h3>
<p>Here are some common poor habits in Flutter development that developers should consider ditching for better performance, maintainability, and user experience:</p>
<ol>
<li><p><strong>Rebuilding Entire Widgets Unnecessarily</strong></p>
<ul>
<li><p>Decreases app performance, especially when rebuilding expensive widgets unnecessarily.</p>
</li>
<li><p>Instead, use const constructors where possible and memoize values to avoid unnecessary builds. Use ValueNotifier or ChangeNotifier for granular updates.</p>
</li>
</ul>
</li>
<li><p><strong>Not Leveraging Flutter's Built-In Widgets</strong></p>
<ul>
<li><p>Reinventing the wheel leads to wasted effort and potential bugs.</p>
</li>
<li><p>Instead, use built-in widgets like ListView, GridView, AnimatedBuilder, and others before creating custom solutions.</p>
</li>
</ul>
</li>
<li><p><strong>Hardcoding Text Styles and Colors</strong></p>
<ul>
<li><p>Results in inconsistent design and difficulty in updating themes.</p>
</li>
<li><p>Instead, use the ThemeData system and TextTheme for consistency across the app.</p>
</li>
</ul>
</li>
<li><p><strong>Ignoring Null Safety</strong></p>
<ul>
<li><p>Increases the risk of runtime crashes due to null exceptions.</p>
</li>
<li><p>Instead, embrace Dart's null safety by using nullable types and non-nullable types appropriately.</p>
</li>
</ul>
</li>
<li><p><strong>Overcomplicating Animations</strong></p>
<ul>
<li><p>Don’t write complex, hard-to-maintain animation code manually.</p>
</li>
<li><p>Instead, use Flutter’s animation APIs like AnimatedContainer, TweenAnimationBuilder, or Flare for simpler implementations.</p>
</li>
</ul>
</li>
<li><p><strong>Not Optimizing Image and Asset Loading</strong></p>
<ul>
<li><p>Leads to increased memory usage and slow app performance.</p>
</li>
<li><p>Instead, use AssetImage with Image.asset or CachedNetworkImage for efficient image loading and caching.</p>
</li>
</ul>
</li>
<li><p><strong>Failing to Use Asynchronous Programming Properly</strong></p>
<ul>
<li><p>Blocking the UI thread with heavy tasks leads to poor responsiveness.</p>
</li>
<li><p>Instead: Use Future, async/await, or Isolate for time-consuming operations.</p>
</li>
</ul>
</li>
<li><p><strong>Not Handling Edge Cases</strong></p>
<ul>
<li><p>Results in unhandled errors or poor user experience.</p>
</li>
<li><p>Instead, use error handling with try-catch, FutureBuilder, and StreamBuilder to manage async errors gracefully.</p>
</li>
</ul>
</li>
<li><p><strong>Using Magic Numbers</strong></p>
<ul>
<li><p>Reduces readability and makes updates harder.</p>
</li>
<li><p>Instead, define constants or use SizedBox and Padding widgets for spacing.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-conclusion-build-better-flutter-apps">Conclusion: Build Better Flutter Apps</h3>
<p>Flutter's power lies in its flexibility, but with great power comes the responsibility to use it wisely. By ditching poor habits like over-relying on <code>MediaQuery</code>, overloading the <code>build</code> method, and defaulting to verbose approaches like <code>StatefulWidget</code> for resource management, you can create apps that are not just functional but elegant, scalable, and efficient.</p>
<p>This journey begins by embracing best practices:</p>
<ul>
<li><p>Use constraint-based layouts and widgets like <code>Flexible</code>, <code>Expanded</code>, and <code>LayoutBuilder</code> to craft truly responsive designs that enhance the user experience across devices.</p>
</li>
<li><p>Simplify resource management with smart solutions like the <code>Disposer</code> widget, making your code more concise and maintainable.</p>
</li>
<li><p>Follow separation of concerns by extracting business logic into dedicated classes or state management solutions, ensuring that your UI remains clean and focused.</p>
</li>
<li><p>Leverage Flutter’s built-in widgets and embrace null safety, asynchronous programming, and consistent theming for better performance and reliability.</p>
</li>
</ul>
<p>By addressing these common challenges and replacing bad habits with smarter approaches, you’ll not only improve the quality of your code but also ensure a smoother development process and a more satisfying user experience. Flutter development is a journey, and every step towards better practices contributes to building apps that truly shine. So, let’s ditch those bad habits and start creating smarter Flutter apps today!</p>
]]></content:encoded></item><item><title><![CDATA[Flutter App Development Done Right]]></title><description><![CDATA[I have been writing Flutter articles for a while about implementing different concepts to your app. Many of you have enjoyed my elaborate step by step…]]></description><link>https://siro.hashnode.dev/flutter-app-development-done-right</link><guid isPermaLink="true">https://siro.hashnode.dev/flutter-app-development-done-right</guid><category><![CDATA[FlutterBestPractices]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Flutter Widgets]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Wed, 11 Sep 2024 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1728150292114/1deb1f00-02d4-4b0d-91af-4886100222e2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been writing Flutter articles for a while about implementing different concepts to your app. Many of you have enjoyed my elaborate step by step sometimes accompanied with code for the same. But I think it’s time I address how to truly harness power of Flutter. I think you need to adhere to a set of best practices that ensure scalability, performance, and maintainability.</p>
<h3 id="heading-in-this-article">In this Article</h3>
<p>This article dives into essential Flutter best practices, from optimizing widget composition and state management to improving code quality with Dart Code Metrics. It builds upon some 2 articles I wrote sometime back in 2022 on <a target="_blank" href="https://dev.siro.co.ke/flutter-tips-and-tricks-1">Tips &amp; Tricks to for Your Flutter App's Performance</a>. Whether you're a beginner or an experienced Flutter developer, following these guidelines will help you build apps that are not only efficient but also future-proof. Let’s explore these practices in detail and understand how they can elevate your Flutter projects.</p>
<hr />
<h3 id="heading-categorization-of-the-best-practices">Categorization of the Best Practices</h3>
<p>To make this simpler for you I have put down a categorized breakdown of the major points into relevant sections for you.</p>
<p><strong>1. Widget Optimization</strong></p>
<ol>
<li><p><strong>Use</strong> <code>const</code> wherever possible: Reduces rebuilds, improves performance.</p>
</li>
<li><p><strong>Choose Widgets Over Methods</strong>: Widgets allow better performance and optimization by Flutter’s build system.</p>
</li>
<li><p><strong>Widget Composition</strong>: Break down complex UIs into smaller, reusable components.</p>
</li>
<li><p><strong>Use constant widgets</strong>: Prevents unnecessary rebuilds, contributing to performance.</p>
</li>
<li><p><strong>Combining Multiple Widgets</strong>: Efficiently manage UI by composing widgets instead of over-relying on complex methods.</p>
</li>
<li><p><strong>Avoid Deeply Nested Widget Hierarchies</strong>: Keep the UI hierarchy shallow to improve readability and performance.</p>
</li>
<li><p><strong>Avoid Returning Widgets</strong>: Instead of returning widgets directly from functions, use other patterns for better control over the UI.</p>
</li>
</ol>
<p><strong>2. State Management</strong></p>
<ol>
<li><p><strong>Efficient state management</strong>: Use appropriate state management techniques like Provider, Riverpod, or BLoC for better scalability and performance.</p>
</li>
<li><p><strong>Use</strong> <code>StatefulWidget</code> Wisely: Only use <code>StatefulWidget</code> where the widget's state truly needs to change.</p>
</li>
<li><p><strong>Use Streams Only When Needed</strong>: Streams are powerful but come with overhead; use them sparingly to avoid complexity and performance bottlenecks.</p>
</li>
<li><p><strong>Avoid Rebuilding Widgets Unnecessarily</strong>: Carefully manage state to minimize widget rebuilds and improve app performance.</p>
</li>
</ol>
<p><strong>3. Code Quality &amp; Efficiency</strong></p>
<ol>
<li><p><strong>Follow Flutter Naming Conventions</strong>: Adhere to standard naming conventions for better readability and maintainability.</p>
</li>
<li><p><strong>Dart Code Metrics</strong>: Use tools like Dart Code Metrics to analyze code complexity and quality.</p>
</li>
<li><p><strong>Choose Third-Party Packages Carefully</strong>: Evaluate packages based on their maintenance, popularity, and impact on performance to avoid bloat.</p>
</li>
<li><p><strong>Software Testing</strong>: Implement robust testing strategies (unit tests, widget tests, integration tests) to ensure app stability and quality.</p>
</li>
</ol>
<p><strong>4. Core Performance</strong></p>
<ol>
<li><p><strong>Build Frames in 16 ms</strong>: Ensure that each frame renders within the 16 ms frame budget to maintain smooth animations and interactions.</p>
</li>
<li><p><strong>Minimize Opacity Use</strong>: Overusing opacity can harm performance; use it cautiously, especially in animations.</p>
</li>
<li><p><strong>Use the Latest Version of Flutter</strong>: Always update to the latest version to benefit from performance improvements and new features.</p>
</li>
<li><p><strong>Limit App Size</strong>: Keep the app size minimal to ensure faster downloads and better user experience, especially on devices with limited storage.</p>
</li>
</ol>
<p><strong>5. Accessibility &amp; User Experience (UX)</strong></p>
<ol>
<li><p><strong>Ensure accessibility of your App</strong>: Make sure your app is accessible to users with disabilities by following accessibility guidelines.</p>
</li>
<li><p><strong>UI Efficiency</strong>: Combine practices such as widget composition, minimizing opacity, and using constant widgets for a more efficient UI design.</p>
</li>
<li><p><strong>UX</strong>: Ensure your app performs well by building frames in 16ms, limiting app size, and following accessibility standards.</p>
</li>
</ol>
<hr />
<h3 id="heading-key-benefits-of-adhering-to-best-practices">Key Benefits of Adhering to Best Practices</h3>
<p>Adhering to Flutter best practices provides several key <strong>benefits</strong>:</p>
<ol>
<li><p><strong>Improved Performance</strong>: Using best practices like <code>const</code> constructors, efficient state management, and avoiding unnecessary widget rebuilds helps reduce the workload on the system, making your app faster and more responsive. Ensuring your app maintains a 16ms frame rate enhances smoothness in animations and user interactions.</p>
</li>
<li><p><strong>Maintainability</strong>: Practices such as widget composition, following naming conventions, and minimizing nested hierarchies lead to cleaner, more readable code. This makes it easier for developers to manage, update, and scale the app over time, especially in team environments.</p>
</li>
<li><p><strong>Scalability</strong>: Efficient state management techniques and modular code structures allow the app to grow with new features while maintaining stability and performance. By using third-party packages carefully and avoiding global variables, developers ensure that the app can scale without becoming unwieldy.</p>
</li>
<li><p><strong>Reduced Memory Usage</strong>: By minimizing widget rebuilds, optimizing opacity use, and keeping the widget tree shallow, you reduce memory consumption, making the app more efficient, particularly on devices with limited resources.</p>
</li>
<li><p><strong>Enhanced Developer Productivity</strong>: Leveraging Dart Code Metrics, using the latest version of Flutter, and structuring widgets properly speed up the development process by simplifying code maintenance and reducing bugs. The use of constant widgets also makes hot reloads faster, boosting productivity.</p>
</li>
<li><p><strong>Accessibility and User Experience</strong>: By following best practices around accessibility and app size optimization, developers ensure that their app reaches a broader audience and offers a better user experience, even on lower-end devices or for users with disabilities.</p>
</li>
<li><p><strong>Code Reusability</strong>: Practices like widget composition and clean code principles make it easier to reuse components across the app, reducing development time for new features and ensuring consistency throughout the project.</p>
</li>
</ol>
<p>Overall, Flutter best practices lead to <strong>better app performance, smoother user experience, more maintainable and scalable codebases</strong>, and <strong>higher developer efficiency</strong>. These practices are crucial to building modern apps that can grow and perform well across a wide variety of devices.</p>
]]></content:encoded></item><item><title><![CDATA[Getting Started with Kotlin Multiplatform]]></title><description><![CDATA[After addressing the different tools available for cross platform development in my earlier article: Cross-Platform Development in 2024 1/3 I have decided…]]></description><link>https://siro.hashnode.dev/getting-started-with-kotlin-multiplatform</link><guid isPermaLink="true">https://siro.hashnode.dev/getting-started-with-kotlin-multiplatform</guid><category><![CDATA[Kotlin]]></category><category><![CDATA[kmp]]></category><category><![CDATA[KMM]]></category><category><![CDATA[desktop]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Mon, 24 Jun 2024 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/lqcvMiBABHw/upload/9af259b75dd71f6e5c1005fae19d95dd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After addressing the different tools available for cross platform development in my earlier article: <a target="_blank" href="https://siro.hashnode.dev/cross-platform-development-in-2024-1">Cross-Platform Development in 2024 1/3</a> I have decided to go forward and share the little I have learnt while exploring the KMP tooling for building cross platform apps. It is going to be an exciting journey for all of us so stay with me it right to the end.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Before we start, ensure you have the following installed on your development machine:</p>
<ol>
<li><p><strong>Android Studio / IntelliJ IDEA</strong> (Up to date version)</p>
</li>
<li><p><strong>Xcode</strong> (Optional but necessary for iOS development)</p>
</li>
<li><p><strong>Node.js</strong> (Optional but necessary for web development)</p>
</li>
</ol>
<p>By default if you install the latest version of Android Studio you should have JDK (Java Development Kit) 8 or higher. I could say the same thing for IntelliJ IDEA.</p>
<h3 id="heading-project-creation">Project Creation</h3>
<p>There are 2 ways of creating a KMP project:</p>
<ol>
<li><p>On JetBrains' website</p>
</li>
<li><p>On Your IDE</p>
</li>
</ol>
<ol>
<li><strong>Creating your KMP Project from JetBrains' Website</strong></li>
</ol>
<p>Using your browser navigate to the address <a target="_blank" href="https://kmp.jetbrains.com/">https://kmp.jetbrains.com/</a> and fill in as well as check the boxes appropriately as in the screenshot below before you can be allowed to download the project to your PC.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720074917631/c16efb10-9298-4eda-94a6-774450b9c531.png" alt class="image--center mx-auto" /></p>
<p>After downloading it, extract to your desired location and from there you should be able to open it from Android Studio which will be our main IDE for this project.</p>
<ol start="2">
<li><strong>Creating your KMP Project from Android Studio</strong></li>
</ol>
<p>You will first have to install the <strong>Kotlin Multiplatform Mobile</strong> plugin on Android Studio before proceeding. After you are down installing restart Android Studio.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720074897186/c73c6de3-8a3e-4190-9041-1505946b4853.png" alt class="image--center mx-auto" /></p>
<p>After restarting Android Studi you will find the new project templates based on the plugin you just installed. Select Kotlin Multiplatfrom App and click on next.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720076120926/5fce2264-056d-4d27-988d-5e3fbfc70c39.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-project-structure">Project Structure</h3>
<p>A typical Kotlin Multiplatform project structure looks like this:</p>
<pre><code class="lang-plaintext">root
├── commonMain
│   └── kotlin
│       └── common code
├── iosMain
│   └── kotlin
│       └── iOS specific code
├── desktopMain
│   └── kotlin
│       └── desktop specific code
├── jsMain
│   └── kotlin
│       └── web specific code
├── build.gradle.kts
└── settings.gradle.kts
</code></pre>
<p>But its worth noting that there is a huge disparity in terms of the project structure created on JetBrains' site and on the IDE.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720076826655/c8ca88b5-e364-4138-a036-f4096f7bdaa4.png" alt class="image--center mx-auto" /></p>
<p>On the left is the project downloaded from JetBrains' site and on the right is one created via Android Studio. My take is the Android Studio plugins is behind what the website plugin offers. You might experience such differences if you clone someone's code and find its different with what you wanted to work with.</p>
<h3 id="heading-configuring-the-build">Configuring the Build</h3>
<p>If you are coming from Android and Kotlin then this current section will be a walk over for you. But if you are new to Android and Kotlin altogether then let's dive into knowing some crucial files that we shall be working with as we work on our Kotlin Multiplatform project.</p>
<ol>
<li><code>build.gradle.kts</code></li>
</ol>
<ol start="2">
<li><p><code>settings.gradle.kts</code></p>
</li>
<li><p><code>libs.versions.toml</code></p>
</li>
</ol>
<ol>
<li><strong>build.gradle.kts</strong></li>
</ol>
<p>The <code>build.gradle.kts</code> file is the main build configuration file for the project. It is written in <strong>Kotlin DSL</strong> (Domain Specific Language) and is used to configure the build process, dependencies, plugins, and other settings for your project.</p>
<h4 id="heading-key-responsibilities">Key Responsibilities:</h4>
<ul>
<li><p><strong>Plugins Configuration</strong>: Specifies which plugins are applied to the project.</p>
</li>
<li><p><strong>Kotlin Multiplatform Configuration</strong>: Defines the targets (e.g., JVM, iOS, JS) and source sets for your KMP project.</p>
</li>
<li><p><strong>Dependencies</strong>: Declares the libraries and frameworks that your project depends on.</p>
</li>
<li><p><strong>Android Configuration</strong>: If your project includes Android, this section specifies Android-specific settings like SDK versions and build types.</p>
</li>
</ul>
<p>You can proceed to open the <code>build.gradle.kts</code> file to configure the build script for your project. Here's a basic example based on my project to get you started:</p>
<pre><code class="lang-kotlin">plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidLibrary)
}

kotlin {
    androidTarget {
        compilations.all {
            kotlinOptions {
                jvmTarget = <span class="hljs-string">"1.8"</span>
            }
        }
    }

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = <span class="hljs-string">"shared"</span>
            isStatic = <span class="hljs-literal">true</span>
        }
    }

    sourceSets {
        commonMain.dependencies {
            <span class="hljs-comment">//put your multiplatform dependencies here</span>
        }
        commonTest.dependencies {
            implementation(libs.kotlin.test)
        }
    }
}

android {
    namespace = <span class="hljs-string">"com.sirodevs.mykpmapp"</span>
    compileSdk = <span class="hljs-number">34</span>
    defaultConfig {
        minSdk = <span class="hljs-number">24</span>
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}
</code></pre>
<ol start="2">
<li><strong>settings.gradle.kts</strong></li>
</ol>
<p>The <code>settings.gradle.kts</code> file is used to configure the overall settings of your Gradle build, including defining the project structure and dependency resolution management.</p>
<h4 id="heading-key-responsibilities-1">Key Responsibilities:</h4>
<ul>
<li><p><strong>Project Inclusion</strong>: Specifies which projects are included in the build.</p>
</li>
<li><p><strong>Dependency Resolution Management</strong>: Configures the version catalogs for managing dependencies.</p>
</li>
</ul>
<p>You can proceed to open the <code>settings.gradle.kts</code> file to configure the build script for your project. Here's a basic example based on my project to get you started:</p>
<pre><code class="lang-kotlin">enableFeaturePreview(<span class="hljs-string">"TYPESAFE_PROJECT_ACCESSORS"</span>)
pluginManagement {
    repositories {
        google()
        gradlePluginPortal()
        mavenCentral()
    }
}

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = <span class="hljs-string">"MyKPMApp"</span>
include(<span class="hljs-string">":androidApp"</span>)
include(<span class="hljs-string">":shared"</span>)
</code></pre>
<ol start="3">
<li><strong>libs.versions.toml</strong></li>
</ol>
<p>The <code>libs.versions.toml</code> file is part of the Gradle Version Catalogs feature, which allows you to centralize and manage the versions of dependencies and plugins in your project.</p>
<h4 id="heading-key-responsibilities-2">Key Responsibilities:</h4>
<ul>
<li><p><strong>Version Management</strong>: Defines the versions of dependencies and plugins used in the project.</p>
</li>
<li><p><strong>Libraries and Plugins</strong>: Lists the libraries and plugins along with their versions, making it easier to manage and update them.</p>
</li>
</ul>
<p>You can proceed to open the <code>libs.versions.toml</code> file to configure the build script for your project. Here's a basic example based on my project to get you started:</p>
<pre><code class="lang-kotlin">[versions]
agp = <span class="hljs-string">"8.5.0"</span>
kotlin = <span class="hljs-string">"2.0.0"</span>
compose = <span class="hljs-string">"1.5.4"</span>
compose-material3 = <span class="hljs-string">"1.1.2"</span>
androidx-activityCompose = <span class="hljs-string">"1.8.0"</span>

[libraries]
kotlin-test = { module = <span class="hljs-string">"org.jetbrains.kotlin:kotlin-test"</span>, version.ref = <span class="hljs-string">"kotlin"</span> }
androidx-activity-compose = { module = <span class="hljs-string">"androidx.activity:activity-compose"</span>, version.ref = <span class="hljs-string">"androidx-activityCompose"</span> }
compose-ui = { module = <span class="hljs-string">"androidx.compose.ui:ui"</span>, version.ref = <span class="hljs-string">"compose"</span> }
compose-ui-tooling = { module = <span class="hljs-string">"androidx.compose.ui:ui-tooling"</span>, version.ref = <span class="hljs-string">"compose"</span> }
compose-ui-tooling-preview = { module = <span class="hljs-string">"androidx.compose.ui:ui-tooling-preview"</span>, version.ref = <span class="hljs-string">"compose"</span> }
compose-foundation = { module = <span class="hljs-string">"androidx.compose.foundation:foundation"</span>, version.ref = <span class="hljs-string">"compose"</span> }
compose-material3 = { module = <span class="hljs-string">"androidx.compose.material3:material3"</span>, version.ref = <span class="hljs-string">"compose-material3"</span> }

[plugins]
androidApplication = { id = <span class="hljs-string">"com.android.application"</span>, version.ref = <span class="hljs-string">"agp"</span> }
androidLibrary = { id = <span class="hljs-string">"com.android.library"</span>, version.ref = <span class="hljs-string">"agp"</span> }
kotlinAndroid = { id = <span class="hljs-string">"org.jetbrains.kotlin.android"</span>, version.ref = <span class="hljs-string">"kotlin"</span> }
kotlinMultiplatform = { id = <span class="hljs-string">"org.jetbrains.kotlin.multiplatform"</span>, version.ref = <span class="hljs-string">"kotlin"</span> }
kotlinCocoapods = { id = <span class="hljs-string">"org.jetbrains.kotlin.native.cocoapods"</span>, version.ref = <span class="hljs-string">"kotlin"</span> }
compose-compiler = { id = <span class="hljs-string">"org.jetbrains.kotlin.plugin.compose"</span>, version.ref = <span class="hljs-string">"kotlin"</span> }
</code></pre>
<p><strong>How They Affect Your KMP Project</strong></p>
<ul>
<li><p><code>build.gradle.kts</code>: This file is essential for configuring your project’s build process. It defines how your project is built, including which plugins to apply, how to configure Kotlin Multiplatform, and which dependencies to include. Any change in this file directly impacts how your project is compiled and packaged.</p>
</li>
<li><p><code>settings.gradle.kts</code>: This file sets up the structure of your project and manages dependency resolution. It is crucial for defining which subprojects are part of your build and ensuring that all dependencies are correctly resolved using the version catalogs.</p>
</li>
<li><p><code>libs.versions.toml</code>: This file provides a centralized way to manage the versions of dependencies and plugins. It makes it easier to maintain consistency across your project by defining all versions in one place. When you need to update a dependency, you only need to change it here, and it will propagate throughout your project.</p>
</li>
</ul>
<h3 id="heading-writing-common-code">Writing Common Code</h3>
<p>Common code includes business logic, data models, and other functionalities that can be shared across platforms. Let's create a simple shared function in the <code>commonMain</code> source set:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">import</span> androidx.compose.animation.AnimatedVisibility
<span class="hljs-keyword">import</span> androidx.compose.foundation.Image
<span class="hljs-keyword">import</span> androidx.compose.foundation.layout.Column
<span class="hljs-keyword">import</span> androidx.compose.foundation.layout.fillMaxWidth
<span class="hljs-keyword">import</span> androidx.compose.material.Button
<span class="hljs-keyword">import</span> androidx.compose.material.MaterialTheme
<span class="hljs-keyword">import</span> androidx.compose.material.Text
<span class="hljs-keyword">import</span> androidx.compose.runtime.*
<span class="hljs-keyword">import</span> androidx.compose.ui.Alignment
<span class="hljs-keyword">import</span> androidx.compose.ui.Modifier
<span class="hljs-keyword">import</span> org.jetbrains.compose.resources.painterResource
<span class="hljs-keyword">import</span> org.jetbrains.compose.ui.tooling.preview.Preview

<span class="hljs-keyword">import</span> mykmpapp.composeapp.generated.resources.Res
<span class="hljs-keyword">import</span> mykmpapp.composeapp.generated.resources.compose_multiplatform

<span class="hljs-meta">@Composable</span>
<span class="hljs-meta">@Preview</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">App</span><span class="hljs-params">()</span></span> {
    MaterialTheme {
        <span class="hljs-keyword">var</span> showContent <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-literal">false</span>) }
        Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
            Button(onClick = { showContent = !showContent }) {
                Text(<span class="hljs-string">"Click me!"</span>)
            }
            AnimatedVisibility(showContent) {
                <span class="hljs-keyword">val</span> greeting = remember { Greeting().greet() }
                Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
                    Image(painterResource(Res.drawable.compose_multiplatform), <span class="hljs-literal">null</span>)
                    Text(<span class="hljs-string">"Compose: <span class="hljs-variable">$greeting</span>"</span>)
                }
            }
        }
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Greeting</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> platform = getPlatform()

    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">greet</span><span class="hljs-params">()</span></span>: String {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello, <span class="hljs-subst">${platform.name}</span>!"</span>
    }
}
</code></pre>
<h3 id="heading-platform-specific-code">Platform-Specific Code</h3>
<p>In a Kotlin Multiplatform project, you typically have a <code>commonMain</code> source set where you define shared code that can be used across different platforms (Android, iOS, desktop, etc.). The <code>MyKMPApp</code> composable function is already defined in the shared module, allowing it to be reused across multiple platforms.</p>
<h4 id="heading-android">Android</h4>
<p>In Android the <code>MainActivity</code> class, is specific to the Android platform. It is responsible for setting up the Android-specific components and displaying the shared composable UI. The <code>setContent</code> function integrates Jetpack Compose with the Android activity lifecycle, allowing you to use the shared composable function within the Android app.</p>
<p>The preview function <code>AppAndroidPreview</code> is useful for development, as it allows you to see how the composable will render on an Android device without having to run the app. This improves development speed and provides immediate visual feedback.</p>
<pre><code class="lang-swift">package com.sirodaves.mykmpapp

<span class="hljs-keyword">import</span> MyKMPApp
<span class="hljs-keyword">import</span> android.os.Bundle
<span class="hljs-keyword">import</span> androidx.activity.ComponentActivity
<span class="hljs-keyword">import</span> androidx.activity.compose.setContent
<span class="hljs-keyword">import</span> androidx.compose.runtime.Composable
<span class="hljs-keyword">import</span> androidx.compose.ui.tooling.preview.Preview

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-title">ComponentActivity</span>() </span>{
    <span class="hljs-keyword">override</span> fun onCreate(savedInstanceState: <span class="hljs-type">Bundle?</span>) {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)

        setContent {
            <span class="hljs-type">MyKMPApp</span>()
        }
    }
}

@<span class="hljs-type">Preview</span>
@<span class="hljs-type">Composable</span>
fun <span class="hljs-type">AppAndroidPreview</span>() {
    <span class="hljs-type">MyKMPApp</span>()
}
</code></pre>
<h4 id="heading-ios">iOS</h4>
<p>For iOS, Kotlin Multiplatform generates a framework that can be used in Xcode. To use the shared code in your iOS app, do the following:</p>
<ol>
<li><p>Open the <code>iosMain</code> folder in Android Studio.</p>
</li>
<li><p>Write your iOS-specific code in Kotlin.</p>
</li>
<li><p>Configure Xcode to use the generated framework.</p>
</li>
</ol>
<p>Create a Swift file in your iOS project to call the shared code:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> androidx.compose.ui.window.ComposeUIViewController

fun <span class="hljs-type">MainViewController</span>() = <span class="hljs-type">ComposeUIViewController</span> { <span class="hljs-type">MyKMPApp</span>() }
</code></pre>
<h4 id="heading-desktop">Desktop</h4>
<p>For desktop applications, your basic code to get your app running on desktop might look like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">import</span> androidx.compose.foundation.layout.*
<span class="hljs-keyword">import</span> androidx.compose.ui.*
<span class="hljs-keyword">import</span> androidx.compose.ui.unit.dp
<span class="hljs-keyword">import</span> androidx.compose.ui.window.*

<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">return</span> application {
        Window(
            onCloseRequest = { exitApplication() },
            title = <span class="hljs-string">"My KMP App"</span>,
            state = rememberWindowState(
                position = WindowPosition.Aligned(Alignment.Center),
                width = <span class="hljs-number">1200</span>.dp,
                height = <span class="hljs-number">700</span>.dp,
            ),
        ) {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                MyKMPApp()
            }
        }
    }
}
</code></pre>
<h4 id="heading-web">Web</h4>
<p>For web applications, Kotlin Multiplatform supports Kotlin/JS. You can use Kotlin/JS with React or other JavaScript frameworks. Here's a basic example using Kotlin/JS:</p>
<ol>
<li>Add the dependency in your <code>build.gradle.kts</code>:</li>
</ol>
<pre><code class="lang-kotlin">implementation(<span class="hljs-string">"org.jetbrains.kotlin-wrappers:kotlin-react:17.0.2-pre.208-kotlin-1.5.0"</span>)
implementation(<span class="hljs-string">"org.jetbrains.kotlin-wrappers:kotlin-react-dom:17.0.2-pre.208-kotlin-1.5.0"</span>)
</code></pre>
<ol start="2">
<li>Write your web-specific code in the <code>jsMain</code> source set:</li>
</ol>
<pre><code class="lang-kotlin"><span class="hljs-keyword">import</span> react.dom.render
<span class="hljs-keyword">import</span> kotlinx.browser.document
<span class="hljs-keyword">import</span> react.RBuilder
<span class="hljs-keyword">import</span> react.dom.h1

<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    render(document.getElementById(<span class="hljs-string">"root"</span>)) {
        MyKMPApp()
    }
}
</code></pre>
<h3 id="heading-running-the-application">Running the Application</h3>
<p>Running your app on Android and iOS is pretty much easy based on either <strong>Android Studio</strong> or <strong>Xcode</strong> respectively. You can get the app to run on a simulator or a real device. Lets focus on getting it to run on a desktop.</p>
<p>To run your KMP Desktop application directly from Android Studio, you need to set up a run configuration.</p>
<ul>
<li><h4 id="heading-1-configure-buildgradlekts">1. Configure <code>build.gradle.kts</code></h4>
<p>  Make sure your <code>build.gradle.kts</code> file is correctly set up for the desktop target. Here's an example configuration:</p>
<pre><code class="lang-kotlin">  kotlin {    
      jvm(<span class="hljs-string">"desktop"</span>) {
          compilations.all {
              kotlinOptions {
                  jvmTarget = <span class="hljs-string">"11"</span>
              }
          }
      }

      sourceSets {
          <span class="hljs-keyword">val</span> desktopMain <span class="hljs-keyword">by</span> getting

          desktopMain.dependencies {
              implementation(compose.desktop.currentOs)
          }
      }
  }

  compose.desktop {
      application {
          mainClass = <span class="hljs-string">"MainKt"</span>

          nativeDistributions {
              targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
              packageName = <span class="hljs-string">"com.sirodevs.mykmpapp"</span>
              packageVersion = <span class="hljs-string">"1.0.0"</span>
          }
      }
  }
</code></pre>
<h4 id="heading-2-set-up-run-configuration-in-android-studio">2. Set Up Run Configuration in Android Studio</h4>
<ol>
<li><p><strong>Open Run/Debug Configurations</strong>:</p>
<p> Go to <code>Run</code> &gt; <code>Edit Configurations...</code> from the top menu.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720092878520/6c584c35-1e53-4ab1-aeb8-0b36f11e0d2c.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><strong>Add a New Configuration</strong>:</p>
<ul>
<li><p>Click the <code>+</code> button to add a new configuration.</p>
</li>
<li><p>Select <code>Gradle</code> from the list.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720093632847/0e36aaae-ccb1-4b3e-a6cf-68e40a7bdf61.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<ul>
<li><p><strong>Name</strong>: Give your configuration a name, e.g., <code>Run Desktop App</code>.</p>
</li>
<li><p><strong>Gradle Project</strong>: Select your project or root module.</p>
</li>
<li><p><strong>Tasks</strong>: Enter <code>:desktop:run</code> (assuming your <code>desktop</code> module is named <code>desktop</code>).</p>
</li>
<li><p><strong>Script Parameters</strong>: Leave this blank.</p>
</li>
<li><p><strong>VM Options</strong>: Optionally, you can set JVM options if needed.</p>
</li>
<li><p><strong>Working Directory</strong>: This should be set to the root directory of your project.</p>
<ol start="3">
<li><strong>Apply and Save</strong>:</li>
</ol>
</li>
<li><p>Click <code>Apply</code> and then <code>OK</code> to save the configuration.</p>
</li>
</ul>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Kotlin Multiplatform allows you to write code once and run it on multiple platforms, significantly improving productivity and maintaining consistency across different environments. By following the steps outlined in this article, you can create a robust Kotlin Multiplatform application that targets iOS, desktop, and web platforms. As the ecosystem grows, more libraries and tools will become available, making Kotlin Multiplatform an even more powerful choice for cross-platform development.</p>
<p>See you in my next one where I will get into more details of making a Droidcon Ke App with KMP!</p>
]]></content:encoded></item><item><title><![CDATA[Fixing the Dart Analysis Server Crashes]]></title><description><![CDATA[You've been diligently working on a groundbreaking Flutter app for your client or company, pouring hours into writing and refining code. You're making…]]></description><link>https://siro.hashnode.dev/fixing-the-dart-analysis-server-crashes</link><guid isPermaLink="true">https://siro.hashnode.dev/fixing-the-dart-analysis-server-crashes</guid><category><![CDATA[dart analysis server]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[dart analyzer]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Sun, 26 May 2024 05:33:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/6Gkj0zb2JXI/upload/49c2f3eec9cbc3a015dbef23e7297bea.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>You've been diligently working on a groundbreaking Flutter app for your client or company, pouring hours into writing and refining code. You're making excellent progress, but suddenly, you hit a snag. As you type or modify code, those familiar red squiggly lines that signal errors or warnings are conspicuously absent. Typically, you rely on these visual cues in VSCode to point out mistakes and guide you toward solutions. Without them, it's like driving without a GPS: you know you're off course, but not exactly where or how to get back on track. You are expecting something like this next image but then it doesn't show up.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716699950216/a40be597-46f4-45e4-9981-d490eb48fb2a.jpeg" alt class="image--center mx-auto" /></p>
<p>This lack of immediate feedback from your trusted IDE can significantly slow down your development process and lead to frustration especially when you rushing to push that PR so that it can be reviewed and merged to stable code. The cop you are expecting to stop you by the roadside and point out your mistake and definitely tell you how to fix it is called the Dart Analysis Server. Now before you what this is I will answer you shortly.</p>
<h3 id="heading-what-is-a-dart-analysis-server"><strong>What is a Dart Analysis Server?</strong></h3>
<p>The analysis server is <strong>a long-running process that provides analysis results to other tools</strong>. It is designed to provide on-going analysis of one or more code bases as those code bases are changing.</p>
<h4 id="heading-key-features-of-the-dart-analysis-server">Key Features of the Dart Analysis Server</h4>
<ol>
<li><p><strong>Real-Time Error Detection and Reporting</strong>: it continuously checks your code for syntax errors, semantic errors, and other issues. It provides immediate feedback with underlines and tooltips directly in your VSCode IDE, helping you identify and correct mistakes as you type.</p>
</li>
<li><p><strong>Code Completion</strong>: it offers intelligent code completions, suggesting methods, properties, and variables that are contextually relevant which accelerates coding by reducing the need to remember every detail of the API or library you are using.</p>
</li>
<li><p><strong>Code Navigation</strong>: it allows you to navigate through your codebase effortlessly. You can jump to the definitions of classes, methods, and variables, making it easier to understand and manage your code structure.</p>
</li>
<li><p><strong>Refactoring Support</strong>: it provides tools for safe and efficient code refactoring. You can rename symbols, extract methods, and perform other refactoring operations, with the server ensuring that these changes are applied consistently throughout your codebase.</p>
</li>
<li><p><strong>Linting and Style Checks</strong>: it enforces coding standards and style guidelines through linting. It highlights code that does not conform to specified rules, helping maintain a consistent and clean codebase.</p>
</li>
<li><p><strong>Quick Fixes and Code Actions</strong>: it often provides quick fixes and code actions that can be applied with a single click. These suggestions can range from correcting simple typos to more complex refactoring.</p>
</li>
</ol>
<h4 id="heading-how-the-dart-analysis-server-works">How the Dart Analysis Server Works</h4>
<p>Now that you know the features of the DAS, I will proceed to tell you how it works so that you may understand how to fix it when it takes an indefinite holiday in the middle of your coding session.</p>
<p>It runs as a background process that communicates with your IDE through a protocol. It maintains an in-memory representation of your project's codebase, which it continuously analyzes as you make changes. This persistent analysis allows it to offer real-time feedback without the need for constant recompilation.</p>
<ul>
<li><p><strong>Initialization</strong>: At initializes the moment you open a Dart project on your IDE and begins parsing the project's files.</p>
</li>
<li><p><strong>Incremental Analysis</strong>: As you edit your code, it incrementally analyzes the changes which minimizes latency such that feedback is provided almost instantaneously.</p>
</li>
<li><p><strong>Communication</strong>: It communicates with your IDE using the Language Server Protocol (LSP). This standardized protocol allows the server to send notifications about errors, suggestions, and code completions to the IDE, which then displays them to you.</p>
</li>
</ul>
<h4 id="heading-benefits-of-using-the-dart-analysis-server">Benefits of Using the Dart Analysis Server</h4>
<ol>
<li><p>Improved Productivity</p>
</li>
<li><p>Enhanced Code Quality</p>
</li>
<li><p>Easier Debugging</p>
</li>
<li><p>Consistent Refactoring</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716701208635/5c0bff1e-f565-47bd-b9da-1d776cb7ec3d.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-troubleshooting-dart-analysis-server-crashes">Troubleshooting Dart Analysis Server Crashes</h3>
<p>If you're working with Flutter and Dart in VS Code, you might occasionally encounter issues where the Dart Analysis Server crashes. This can disrupt your development workflow by causing problems like errors not showing up in the editor until you build the project. Here are some steps and tips to help you fix and prevent the Dart Analysis Server from crashing.</p>
<h4 id="heading-1-ensure-you-have-the-latest-versions">1. Ensure You Have the Latest Versions</h4>
<p>Update VSCode and Extensions:</p>
<ul>
<li><p>Open VSCode and go to the Extensions view by clicking the Extensions icon in the Activity Bar on the side of the window.</p>
</li>
<li><p>Search for "Flutter" and "Dart" extensions and check if there are updates available. Update them if needed.</p>
</li>
<li><p>Additionally, make sure your VSCode installation is up to date by going to Help &gt; Check for Updates.</p>
</li>
</ul>
<h4 id="heading-2-clear-the-dart-analysis-server-cache">2. Clear the Dart Analysis Server Cache</h4>
<p>Sometimes, corrupted cache files can cause the analysis server to crash. Clearing the cache can help resolve this.</p>
<p><strong>Locate and Delete Cache Files:</strong></p>
<ul>
<li><p><strong>macOS and Linux:</strong></p>
<ol>
<li><p>Open a terminal window.</p>
</li>
<li><p>Navigate to your home directory:</p>
<pre><code class="lang-sh"> <span class="hljs-built_in">cd</span> ~
</code></pre>
</li>
<li><p>List all files, including hidden ones:</p>
<pre><code class="lang-sh"> ls -a
</code></pre>
</li>
<li><p>Navigate to the <code>.dartServer</code> directory:</p>
<pre><code class="lang-sh"> <span class="hljs-built_in">cd</span> .dartServer
</code></pre>
</li>
<li><p>Delete the contents of the directory:</p>
<pre><code class="lang-sh"> rm -rf *
</code></pre>
</li>
</ol>
</li>
<li><p><strong>Windows:</strong></p>
<ol>
<li><p>Open File Explorer.</p>
</li>
<li><p>Navigate to your user directory (e.g., <code>C:\Users\YourUsername</code>).</p>
</li>
<li><p>Ensure that hidden files are visible (View &gt; Hidden items).</p>
</li>
<li><p>Find and open the <code>.dartServer</code> directory.</p>
</li>
<li><p>Select all contents and delete them.</p>
</li>
</ol>
</li>
</ul>
<h4 id="heading-3-restart-the-dart-analysis-server">3. Restart the Dart Analysis Server</h4>
<p>After clearing the cache, restart the Dart Analysis Server to reinitialize it.</p>
<p><strong>Restart the Analysis Server:</strong></p>
<ol>
<li><p>Open VSCode.</p>
</li>
<li><p>Open the Command Palette by pressing <code>Ctrl+Shift+P</code> (or <code>Cmd+Shift+P</code> on macOS).</p>
</li>
<li><p>Type and select "Dart: Restart Analysis Server".</p>
</li>
</ol>
<h4 id="heading-4-adjust-vscode-settings">4. Adjust VSCode Settings</h4>
<p>Certain settings in VSCode can help manage the performance and stability of the Dart Analysis Server.</p>
<p><strong>Enable Auto-Save:</strong></p>
<ul>
<li>Go to File &gt; Auto Save to enable auto-saving of your work. This ensures that changes are continuously checked by the analyzer.</li>
</ul>
<p><strong>Exclude Large or Unnecessary Files from Analysis:</strong></p>
<ul>
<li><p>Open your VSCode settings (File &gt; Preferences &gt; Settings or by pressing <code>Ctrl+,</code>).</p>
</li>
<li><p>Search for "dart.analysisExcludedFolders" and add paths to folders that you don't need to be analyzed, such as build directories or large dependency folders.</p>
</li>
</ul>
<h4 id="heading-5-check-for-syntax-errors-and-linter-configuration">5. Check for Syntax Errors and Linter Configuration</h4>
<p>Syntax errors and misconfigured linters can sometimes cause the analysis server to fail.</p>
<p><strong>Check for Syntax Errors:</strong></p>
<ul>
<li>Carefully review your code for any syntax errors or typos that might disrupt the analysis process.</li>
</ul>
<p><strong>Verify Linter Configuration:</strong></p>
<ul>
<li>Ensure that you have a proper <code>analysis_options.yaml</code> file in the root of your Flutter project and that it does not contain any rules that might be suppressing error messages or causing conflicts.</li>
</ul>
<h4 id="heading-6-reinstall-extensions">6. Reinstall Extensions</h4>
<p>If problems persist, try reinstalling the Flutter and Dart extensions.</p>
<p><strong>Reinstall Extensions:</strong></p>
<ol>
<li><p>Uninstall the Flutter and Dart extensions from the Extensions view.</p>
</li>
<li><p>Close and reopen VSCode.</p>
</li>
<li><p>Reinstall the extensions from the Extensions view.</p>
</li>
</ol>
<h4 id="heading-7-create-a-new-project">7. Create a New Project</h4>
<p>Sometimes issues can be project-specific. Creating a new Flutter project can help determine if the problem lies within your current project setup.</p>
<p><strong>Create a New Flutter Project:</strong></p>
<ol>
<li><p>Open the Command Palette (<code>Ctrl+Shift+P</code>).</p>
</li>
<li><p>Select "Flutter: New Project" and create a new project.</p>
</li>
<li><p>Check if the new project has the same issue.</p>
</li>
</ol>
<h3 id="heading-conclusion">Conclusion</h3>
<p>By following these steps, you should be able to resolve most issues related to the Dart Analysis Server crashing in VSCode. Keeping your tools up to date, managing your project files effectively, and ensuring proper configuration are key to maintaining a smooth development experience. If the issue persists, consider seeking help from the Flutter community online hoping you are a member of one, otherwise we have StackOverFlow, Github and the official Flutter as well as Dart documentation.</p>
]]></content:encoded></item><item><title><![CDATA[Setting up Flutter as a Beginner]]></title><description><![CDATA[A Beginner-Friendly Guide for Windows, macOS, Linux, and Chrome OS]]></description><link>https://siro.hashnode.dev/setting-up-flutter-as-a-beginner</link><guid isPermaLink="true">https://siro.hashnode.dev/setting-up-flutter-as-a-beginner</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><category><![CDATA[VS Code]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Tue, 09 Apr 2024 11:12:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/OqOhYRjn_JY/upload/013b0897b34778199e01aaff2677ba95.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-getting-started-with-flutter">Getting Started with Flutter</h1>
<p>Flutter is an exciting framework that allows you to build beautiful cross-platform mobile applications. If you're new to Flutter, don't worry – setting it up is easier than you think! In this guide, we'll walk you through the installation process on Windows, macOS, Linux, and even Chrome OS. We'll cover both Android Studio, Visual Studio Code and even IntelliJ IDEA to make sure you have the tools you need to start coding.</p>
<h2 id="heading-installing-android-sdk">Installing Android SDK</h2>
<p>Installing Android Studio and its SDK is a crucial step for Flutter development. Below, I'll provide a beginner-friendly guide for installing Android Studio on Windows, macOS, and Linux.</p>
<ol>
<li><p><strong>Download Android Studio:</strong></p>
<ul>
<li><p>Visit <a target="_blank" href="http://developer.android.com/studio">developer.android.com/studio</a></p>
</li>
<li><p>Download the installer for your operating system.</p>
</li>
</ul>
</li>
<li><p><strong>Install Android Studio</strong></p>
<ol>
<li>When it comes to installing depending on the OS you are in instructions may differ as below.</li>
</ol>
</li>
</ol>
<ul>
<li><p>For Windows, run the .<strong>exe</strong> installer.</p>
</li>
<li><p>For macOS, open the downloaded <strong>.dmg</strong> file and drag Android Studio to the "Applications" folder.</p>
</li>
<li><p>For Linux, extract and run the installer in the <code>bin</code> directory using <code>./studio.sh</code>.</p>
</li>
<li><p>For chromeOS, just follow the Linux instructions after enabling Linux support on your Chromebook.</p>
</li>
</ul>
<ol start="2">
<li><p>Follow the wizard instructions for installing once the installer is running.</p>
</li>
<li><p>Install Android Virtual Device (AVD) during setup.</p>
</li>
</ol>
<ol start="3">
<li><p><strong>Launch Android Studio</strong></p>
<ol>
<li><p>Configure Flutter in Android Studio by installing the Flutter and Dart plugins before restarting the IDE.</p>
</li>
<li><p>Access the SDK Manager, click on "Configure" at the bottom right corner and select "SDK Manager."</p>
</li>
<li><p>Install Command-Line Tools:</p>
<ul>
<li><p>In the SDK Platforms tab, select "Show Package Details."</p>
</li>
<li><p>Look for "Android SDK Command-line Tools" and check the box.</p>
</li>
<li><p>Click "Apply" to install.</p>
</li>
</ul>
</li>
</ol>
</li>
</ol>
<h2 id="heading-installing-flutter-sdk">Installing Flutter SDK</h2>
<p>To install the Flutter SDK, follow these general instructions. I'll specify any OS-specific steps where needed:</p>
<ol>
<li><p><strong>Downloading and Extracting</strong></p>
<ol>
<li><p>Download the Flutter SDK from the <a target="_blank" href="https://docs.flutter.dev/get-started/install">official website</a>.</p>
</li>
<li><p>Extract the ZIP file to a preffred location on your machine.</p>
</li>
</ol>
</li>
<li><p><strong>Adding Flutter to the system Path variable</strong></p>
<p> For Windows:</p>
<ol>
<li><p>Press <code>Win + S</code> to open the search bar.</p>
</li>
<li><p>Type "Environment Variables" and select "Edit the system environment variables" to open it.</p>
</li>
<li><p>In the System Properties window, click the "Environment Variables..." button.</p>
</li>
<li><p>In the Environment Variables window, under the "System variables" section, find and select the "Path" variable. Click "Edit."</p>
</li>
<li><p>Click "New" and add the path to the Flutter <code>bin</code> directory (e.g., <code>D:\Devs\flutter\bin</code>).</p>
</li>
<li><p>Click "OK" to close each window.</p>
</li>
<li><p>Open a new Command Prompt or PowerShell window and run <code>flutter --version</code> to verify that Flutter is in the PATH.</p>
</li>
</ol>
</li>
</ol>
<h3 id="heading-macoslinux">macOS/Linux:</h3>
<ol>
<li><p>Open a terminal window.</p>
</li>
<li><p>Depending on your shell (bash, zsh, etc.), open the appropriate profile file. For example:</p>
<ul>
<li><p>For bash: <code>nano ~/.bashrc</code> or <code>nano ~/.bash_profile</code></p>
</li>
<li><p>For zsh: <code>nano ~/.zshrc</code></p>
</li>
</ul>
</li>
<li><p>Add the following line at the end of the file to add Flutter to PATH:</p>
</li>
</ol>
<pre><code class="lang-bash">    <span class="hljs-built_in">export</span> PATH=<span class="hljs-string">"<span class="hljs-variable">$PATH</span>:/home/sirodevs/flutter/bin"</span>
</code></pre>
<ol start="4">
<li><p>Replace <code>/home/sirodevs/flutter</code> with the actual path to your Flutter installation.</p>
</li>
<li><p>Save the file (press <code>Ctrl + X</code>, then <code>Y</code>, then <code>Enter</code> in nano).</p>
</li>
</ol>
<blockquote>
<p>You could also run this on your terminal as a shortcut to avoid opening the nano editor</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">'export PATH="$PATH:/home/sirodevs/flutter/bin"'</span> &gt;&gt; ~/.bashrc
</code></pre>
</blockquote>
<ol start="6">
<li><p>Run <code>source ~/.bashrc</code> (or <code>source ~/.zshrc</code>) to apply the changes and reload the profile.</p>
</li>
<li><p>Open a new terminal window and run <code>flutter --version</code> to verify that Flutter is in the PATH.</p>
</li>
</ol>
<p>These instructions should help you add Flutter to the system PATH variable on your specific operating system. After completing these steps, you should be able to use Flutter commands from any directory in the terminal or command prompt.</p>
<p>user in plugdev group; are your udev rules wrong or error: insufficient permissions for device: udev requires plugdev group membership?</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">'export ANDROID_SDK_ROOT="/home/sirodevs/Developer/Android/Sdk"'</span> &gt;&gt; ~/.bashrc
</code></pre>
<p>This article is not finished yet, I will keep updating it and adjusting it as we go along.</p>
]]></content:encoded></item><item><title><![CDATA[Choosing a Cross Platform Tool in 2024]]></title><description><![CDATA[How do you choose you Cross Platform Tool for your next big app]]></description><link>https://siro.hashnode.dev/choosing-a-cross-platform-tool-in-2024</link><guid isPermaLink="true">https://siro.hashnode.dev/choosing-a-cross-platform-tool-in-2024</guid><category><![CDATA[Cross Platform App Development. ]]></category><category><![CDATA[Cordova]]></category><category><![CDATA[ReactHooks]]></category><category><![CDATA[PWA]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Kotlin Multiplatform]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Tue, 13 Feb 2024 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/hGV2TfOh0ns/upload/b3a37822f814d7923ebaab3481c6e4e5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>After examining the comprehensive comparison between cross-platform and native development, along with exploring the shared principles and complexities inherent in cross-platform development – encompassing coding, compiling, testing, and debugging processes – it is now time to address the elephant in the room.</p>
<p>With 6 distinguished options: Cordova, React Native, Progressive Web Apps (PWAs), Xamarin (C#), Flutter (Dart) and Kotlin Multiplatform (KMP), it can be hard to choose among the cross-platform frameworks, especially if you are a CTO or project manager.</p>
<h3 id="heading-factors-to-consider">Factors to Consider</h3>
<ol>
<li><p><strong>Framework Evaluation Factors:</strong></p>
<ul>
<li><p><strong>Cost Considerations:</strong> Assess the overall cost of adopting the framework, including licensing fees, development expenses, and potential savings over time.</p>
</li>
<li><p><strong>Time-to-Market:</strong> Examine how quickly the framework allows you to bring your application to market, considering project timelines and deadlines.</p>
</li>
<li><p><strong>Development Costs:</strong> Evaluate the overall development costs associated with the framework, including initial setup and ongoing maintenance.</p>
</li>
</ul>
</li>
<li><p><strong>Developer's Perspective:</strong></p>
<ul>
<li><p><strong>Learning Curve:</strong> Consider the learning curve associated with each framework. A smoother learning curve can contribute to faster development.</p>
</li>
<li><p><strong>Native Platform Knowledge:</strong> Examine the level of knowledge of native platforms required. Some frameworks may demand more familiarity with iOS or Android development.</p>
</li>
</ul>
</li>
<li><p><strong>Practical Considerations:</strong></p>
<ul>
<li><p><strong>Development Tools:</strong> Assess the available development tools, including integrated development environments (IDEs) and debugging capabilities.</p>
</li>
<li><p><strong>Code Maintenance:</strong> Explore the feasibility of maintaining a single code base across platforms. Consider factors such as code readability and ease of updates.</p>
</li>
</ul>
</li>
<li><p><strong>Team Expertise:</strong></p>
<ul>
<li><p><strong>Existing Team Skills:</strong> Leverage the expertise of your existing team. The skills they possess may heavily influence the choice of a framework.</p>
</li>
<li><p><strong>Hands-On Testing:</strong> Experiment with each framework to determine how well it aligns with your team's needs and workflow.</p>
</li>
</ul>
</li>
<li><p><strong>Real-world Scenario:</strong></p>
<ul>
<li><strong>Current Project Status:</strong> Acknowledge that your project might not be starting from scratch. Consider the existing codebase and requirements when making your decision.</li>
</ul>
</li>
</ol>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Remember, the effectiveness of a framework can vary based on the unique needs and circumstances of your team and project. Practical testing and hands-on experience are often the most reliable methods for finding the best fit.</div>
</div>

<h3 id="heading-the-best-way-to-choose">The best way to choose</h3>
<p>You can try and divide your choosing in phases or steps as below:</p>
<ol>
<li><p><strong>Exploration/Research:</strong></p>
<ul>
<li><p><strong>Quality Time Investment:</strong> Spend time exploring each cross-platform framework individually to understand their nuances and capabilities.</p>
</li>
<li><p><strong>Support System and Documentation:</strong> Evaluate the available support system, documentation, and tools provided by each framework.</p>
</li>
<li><p><strong>Template and Example Availability:</strong> Check if frameworks offer templates or examples that can serve as a starting point for your app development.</p>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>Research and Comparisons:</strong></p>
<ul>
<li><p><strong>Framework Websites:</strong> Begin your exploration by visiting the official websites of each framework to gather initial insights.</p>
</li>
<li><p><strong>Web Search and Opinions:</strong> Conduct a web search to find opinions and reviews on each tool, gaining diverse perspectives.</p>
</li>
<li><p><strong>Comparison Articles:</strong> Seek articles comparing the strengths and weaknesses of each framework in relation to one another.</p>
</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p><strong>Consider Team's Expertise:</strong></p>
<ul>
<li><p><strong>Assess Team's Talents:</strong> Prioritize evaluating your team's existing skills and expertise before delving into cross-platform frameworks.</p>
</li>
<li><p><strong>Tailor Choice to Talents:</strong> Align your choice with your team's strengths; for instance, if your team leans towards web development, hybrid frameworks or PWAs may be more suitable.</p>
</li>
</ul>
</li>
</ol>
<ol start="4">
<li><p><strong>Subjectivity in Decision-Making:</strong></p>
<ul>
<li><p><strong>Recognize Subjectivity:</strong> Acknowledge that framework selection often involves subjective elements, and tailor your choice to your team's strengths.</p>
</li>
<li><p><strong>Personalized Approach:</strong> Understand that the best decision may differ for each team, and the key is to tailor the choice based on existing talents.</p>
</li>
</ul>
</li>
<li><p><strong>Empowerment and Final Steps:</strong></p>
<ul>
<li><p><strong>Information for Decision:</strong> Ensure you have gathered enough information to make an informed decision that suits your team and project needs.</p>
</li>
<li><p><strong>Create Your App:</strong> Armed with insights, move forward to create your cross-platform app, leveraging the chosen framework that aligns with your team's expertise.</p>
</li>
</ul>
</li>
</ol>
<p>This article aims to empower you with the knowledge needed to embark on your cross-platform app development journey. Good luck in creating that outstanding app for your team!</p>
]]></content:encoded></item><item><title><![CDATA[Cross-Platform Development in 2024 3/3]]></title><description><![CDATA[In my previous article Cross-Platform Development in 2024 2/3 I explored 3 cross platform tools: Cordova, Progressive Web Apps (PWAs) and React Native. In…]]></description><link>https://siro.hashnode.dev/cross-platform-development-in-2024-3</link><guid isPermaLink="true">https://siro.hashnode.dev/cross-platform-development-in-2024-3</guid><category><![CDATA[cross platform development]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[React Native]]></category><category><![CDATA[PWA]]></category><category><![CDATA[KMM]]></category><category><![CDATA[kmp]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Mon, 29 Jan 2024 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709400747217/888b338d-0050-4f93-8022-91b436053c1d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction"><strong>Introduction</strong></h2>
<p>In my previous article <a target="_blank" href="https://siro.hashnode.dev/cross-platform-development-in-2024-2">Cross-Platform Development in 2024 2/3</a> I explored 3 cross platform tools: Cordova, Progressive Web Apps (PWAs) and React Native. In this article I will proceed to explore the other 3: Xamarin, Flutter and Kotlin Multiplatform. So let's jump right into it.</p>
<h2 id="heading-xamarin">Xamarin</h2>
<h3 id="heading-what-is-xamarin">What is Xamarin?</h3>
<p>Xamarin, built on the <a target="_blank" href="http://open-source.NET">open-source.NET</a> platform and later acquired by Microsoft, is a cross-platform development framework utilizing the C# programming language. With support for iOS, Android, Windows and Mac, it facilitates the creation of native apps. Integrated into Microsoft's Visual Studio, Xamarin primarily leverages this robust integrated development environment, although developers have the technical flexibility to write Xamarin code in various editors. A notable advantage of Xamarin lies in its capacity to segregate shared business logic from the app's user interface, allowing an estimated 70 to 90% of code in the business logic tier to be reused across different platforms.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709474985404/a453c13e-14e4-46ad-9e92-d47dc5e3c8c0.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-xamarin-app-architecture">Xamarin App Architecture</h3>
<p>Xamarin offers the Xamarin.Forms package, enabling developers to create the UI using C#, establishing a unified development stack for multiple platforms. The development process for Xamarin apps revolves around C#, utilizing platform-specific libraries to craft business logic for cross-platform applications. Optionally, Xamarin.Forms can be employed to develop the UI for all platforms using C#. The Xamarin framework automates the compilation of apps for supported platforms and is adept at handling memory allocation, garbage collection, and platform interoperability.</p>
<p>The outcome is applications that are frequently likened to native ones, boasting comparable performance and user experience on both iOS and Android mobile development platforms. A Xamarin app comprises shared business logic, often covering aspects like database and network access in C#. The UI can be coded in C# using Xamarin.Forms or natively, inside or outside of Visual Studio, resulting in a Platform Native App compatible with platforms such as Android, iOS, Windows, and Mac.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709476640683/a7893c83-cd67-4b95-863f-1d999519c9c9.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-xamarins-strength">Xamarin's Strength</h3>
<ol>
<li><p><strong>Unified Development:</strong> It offers a single entry point for mobile development across various platforms, enabling developers to use one language (C#) and one tool (Visual Studio) to target iOS, Android, Windows and Mac. This results in a comprehensive end-to-end solution in the cross-platform market space.</p>
</li>
<li><p><strong>Performance Parity:</strong> Xamarin apps deliver performance comparable to native apps in Java, Objective-C, or Swift, ensuring a seamless user experience.</p>
</li>
<li><p><strong>Consistent UI:</strong> Xamarin provides UI components through Xamarin Forms or native UI development, ensuring that Xamarin apps maintain the look and feel of native apps across platforms.</p>
</li>
<li><p><strong>Device Functionality:</strong> Xamarin apps seamlessly integrate with common device functionality across platforms using plugins and specific APIs, eliminating hardware compatibility issues.</p>
</li>
<li><p><strong>Native Libraries Linking:</strong> Xamarin supports linking with native libraries, allowing for better customization and the incorporation of native-level functionality with minimal overhead.</p>
</li>
<li><p><strong>Open Source and Backed by Microsoft:</strong> The Xamarin SDK is open source and backed by Microsoft, ensuring continuous support and development. Microsoft's commitment to building apps for Android and iOS positions Xamarin as a key player in this ecosystem.</p>
</li>
<li><p><strong>Strong Developer Community:</strong> Xamarin benefits from a strong community of developers skilled in C# and the .NET platform, making it comparable to finding developers for writing native apps. Xamarin developers can target multiple platforms, unlike native developers who usually focus on a single platform.</p>
</li>
<li><p><strong>Integrated and a Cross-Platform Development in Visual Studio:</strong> Xamarin development is primarily done in Visual Studio, offering first-class support for creating Xamarin apps. Visual Studio allows building native UIs for Android and iOS seamlessly using drag-and-drop tools. For those new to Visual Studio, it provides a unified environment for creating cross-platform native apps without the need for patching together various tools.</p>
</li>
<li><p><strong>Xamarin Live Player:</strong> This tool enables continuous deployment, testing, and debugging directly on iOS and Android devices, streamlining the development and testing process.</p>
</li>
</ol>
<h2 id="heading-flutter">Flutter</h2>
<h3 id="heading-what-is-flutter">What is Flutter?</h3>
<p>Flutter, a cross-platform native app framework developed by Google, is an open-source solution that generates native apps for Android, iOS, web, Linux, Windows and Mac. Its seamless integration with development tools such as Android Studio, IntelliJ and Visual Studio is facilitated by plugins dedicated to the Flutter framework and Dart programming language.</p>
<h3 id="heading-flutter-app-architecture">Flutter App Architecture</h3>
<p>Flutter employs a unified set of tools and technologies for diverse platforms, primarily utilizing the Dart language to construct Flutter widgets. These widgets, written in Dart, are compiled into native apps by the Flutter framework, delivering applications compatible with Android, iOS, web and desktop platforms.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709481514124/988539df-6eb3-4775-8fcf-798ddbb8a3f5.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-flutters-strengths">Flutter's Strengths</h3>
<ol>
<li><p><strong>High-Speed Deployment:</strong> Similar to React Native, Flutter excels in delivering fast, native-speed applications. By generating native apps, Flutter ensures performance comparable to other native apps on the device, eliminating the slower nature of hybrid apps running within platform-specific web components.</p>
</li>
<li><p><strong>User Interface Development:</strong> At its core, Flutter is a user interface development tool that revolves around the concept of widgets. Every element in a Flutter app is a widget, enabling developers to compose custom widgets as needed. These widgets are compiled to utilize the same UI components as regular iOS, Android apps web apps or desktop apps, with Dart replacing Swift, Objective-C, or Java or Javascript or C/C++ for assembly.</p>
</li>
<li><p><strong>Declarative Programming with Dart:</strong> Flutter employs Dart, a modern and expressive programming language that adopts a declarative approach. Declarative languages enable developers to focus on the "what" rather than the "how," streamlining the development process. This approach allows Flutter developers to maintain a single code base for both Android and iOS.</p>
</li>
<li><p><strong>Platform Independence:</strong> Flutter effectively manages platform differences, freeing developers to concentrate on the app's functionality rather than the intricacies of various platforms. This results in a unified development experience across Android and iOS platforms.</p>
</li>
<li><p><strong>Hot Reload Feature:</strong> Flutter incorporates a powerful feature called hot reload, allowing developers to experiment with code modifications while the app is running. It facilitates real-time debugging and correction of crashes without interrupting the development flow.</p>
</li>
<li><p><strong>Modern Framework with Built-in Widgets:</strong> As a modern framework reminiscent of React, Flutter includes a 2D rendering engine and pre-built widgets that adhere to material design for Android and Cupertino themes for iOS. This approach streamlines the development of visually appealing and consistent applications.</p>
</li>
<li><p><strong>Integration with Developer Tools:</strong> Flutter has strong ties with popular developer tools, including Android Studio, IntelliJ and Visual Studio. This integration enhances the development environment, making it convenient for developers to work seamlessly within their preferred IDEs.</p>
</li>
</ol>
<h2 id="heading-kotlin-multiplatform-kmp">Kotlin Multiplatform (KMP)</h2>
<h3 id="heading-what-is-kmp-what-is-kmm">What is KMP? What is KMM?</h3>
<p><strong>Kotlin Multiplatform (KMP)</strong> is a technology developed by <strong>JetBrains</strong> that allows you to write code in Kotlin that can be shared and run across multiple platforms like Android, iOS, web (JavaScript), desktop (Windows, macOS, Linux).</p>
<p><strong>Kotlin Multiplatform Mobile (KMM)</strong> is extension of <strong><em>KMP</em></strong>, specifically tailored for mobile application development with a primary focus on Android and iOS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709484002578/aa401fb4-0232-4f81-aebe-02a4d6e5e232.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-kmp-app-architecture">KMP App Architecture</h3>
<p><strong>KMP</strong> aims to address the challenge of developing and maintaining separate codebases for different platforms, which can be time-consuming and error-prone. With KMP, you can write common business logic, data models, and utility functions in Kotlin and then target specific platform-specific implementations for user interfaces, networking, and other platform-dependent features.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709484363557/3de7f1c9-a270-4529-8b74-fe567a37b899.png" alt class="image--center mx-auto" /></p>
<p>Sharing code between mobile platforms is a major Kotlin Multiplatform use case. With Kotlin Multiplatform, you can build cross-platform mobile applications that share code between Android and iOS projects to implement networking, data storage and data validation, analytics, computations, and other application logic.</p>
<h3 id="heading-kmps-strengths">KMP's Strengths</h3>
<ol>
<li><p><strong>Shared Code:</strong> Develop the essential logic of your application or library in Kotlin, creating code that can be shared across diverse platforms. This code, usually platform-agnostic, encompasses business logic, algorithms, and data models.</p>
</li>
<li><p><strong>Shared Interfaces:</strong> Define shared interfaces or APIs with Kotlin Multiplatform, enabling different implementations on each platform. This ensures a consistent API while accommodating platform-specific requirements.</p>
</li>
<li><p><strong>Gradle Integration:</strong> Manage Kotlin Multiplatform projects seamlessly with the Gradle build system. Gradle facilitates the configuration and building of shared code and platform-specific implementations.</p>
</li>
<li><p><strong>Code Sharing:</strong> Reuse code across platforms to minimize duplication, enhance maintainability, and potentially expedite development. While some platform-specific code may still be necessary for an optimal user experience, Kotlin Multiplatform aims to maximize code reuse.</p>
</li>
<li><p><strong>Platform-Specific Code:</strong> Tailor platform-specific implementations for each target platform (e.g., Android, iOS, JavaScript), addressing interactions with the platform’s APIs, UI components, and other features.</p>
</li>
</ol>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">In summary, Kotlin Multiplatform serves as a robust tool for developers aiming to create cross-platform applications or libraries using Kotlin. It streamlines the process of targeting multiple platforms without compromising code quality or maintainability. Keep in mind that KMP's features may evolve, so referring to official documentation and resources for the latest information is advisable.</div>
</div>

<h2 id="heading-other-options">Other Options</h2>
<p>Having looked at a few of the more popular Cross-platform frameworks, I would like to briefly mention some that you may not have heard of or know little about:</p>
<ol>
<li><p><strong>Corona SDK:</strong> For Cross-platform game development, explore Corona SDK. It's a gaming engine that utilizes the Lua Programming Language to construct Cross-platform apps, providing full access to the native system.</p>
</li>
<li><p><strong>NativeScript:</strong> This is a Cross-platform JavaScript framework generating platform-native UIs with Angular, TypeScript, and JavaScript. Developers can choose their preferred tools, and NativeScript can be installed as a Visual Studio plugin. Learn more at the NativeScript homepage.</p>
</li>
<li><p><strong>Ionic Framework:</strong> An open-source framework for hybrid mobile apps, Ionic is built on Cordova and supports Angular JS, TypeScript, HTML, CSS, and JavaScript for app development. It offers starter templates, additional native plugins, and app themes. Visit the Ionic Framework website for more details.</p>
</li>
<li><p><strong>Codename One:</strong> If you're a Java developer, Codename One is worth checking out. It functions as a plugin for Eclipse, NetBeans, and IntelliJ, offering a second-generation drag-and-drop GUI builder. It's free and open source, allowing you to stay within the Java language for your development needs.</p>
</li>
</ol>
<hr />
<p>In another article I plan to talk about how you can choose a cross platform to use in your next massive cross platform project: <a target="_blank" href="https://siro.hashnode.dev/choosing-a-cross-platform-tool-in-2024">Choosing a Cross Platform Tool in 2024</a>. So see you there.</p>
]]></content:encoded></item><item><title><![CDATA[Cross-Platform Development in 2024 2/3]]></title><description><![CDATA[In my previous article Cross-Platform Development in 2024 1/3 I explored the realm of cross-platform app development, highlighting: Its benefits such as…]]></description><link>https://siro.hashnode.dev/cross-platform-development-in-2024-2</link><guid isPermaLink="true">https://siro.hashnode.dev/cross-platform-development-in-2024-2</guid><category><![CDATA[Cross Platform App Development. ]]></category><category><![CDATA[Android]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Cordova]]></category><category><![CDATA[React Native]]></category><category><![CDATA[KMM]]></category><dc:creator><![CDATA[Siro Devs]]></dc:creator><pubDate>Wed, 24 Jan 2024 09:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709398077524/57cf7195-7595-47ba-897b-6ac14fb75be9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>In my previous article <a target="_blank" href="https://siro.hashnode.dev/cross-platform-development-in-2024-1">Cross-Platform Development in 2024 1/3</a> I explored the realm of cross-platform app development, highlighting:</p>
<blockquote>
<ul>
<li><p>Its benefits such as skill set consolidation, code re-usability, streamlined maintenance, and cost savings.</p>
</li>
<li><p>Insights into when cross-platform development may not be ideal, such as specific platform targeting or low-level hardware requirements.</p>
</li>
<li><p>Details of the construction of hybrid cross-platform apps using HTML, CSS, and JavaScript, and drawing a parallel to Java applications.</p>
</li>
<li><p>Cross-Platform Native Apps, categorizing them into two types based on business logic and UI code.</p>
</li>
</ul>
</blockquote>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The overarching comparison between cross-platform and native development underscores the shared principles and complexities involved in cross-platform development, included coding, compiling, testing, and debugging processes.</div>
</div>

<p>This is a continuation of that article in a 3 part series of articles intended to help you choose a cross-platform framework for your next mobile app project. I plan to explore five of the most popular cross-platform frameworks, Cordova, React Native, Xamarin, Flutter, and Progressive web apps. For each of the platforms, we'll talk about the tools and technologies used by the platform and where the framework excels. So let's proceed and address 3 of them.</p>
<h2 id="heading-cordova">Cordova</h2>
<h3 id="heading-what-is-cordova">What is Cordova?</h3>
<p>Cordova is an open-source command line tool that enables mobile app developers to create hybrid apps for various platforms using web technologies like HTML5, CSS3, and JavaScript. It functions as a build tool, transforming web applications into native apps for supported platforms.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Cordova doesn't offer a user interface and doesn't follow a project-based metaphor for file organization. It distinguishes itself from traditional development tools by not serving as an editor or IDE. It's not a programming language; rather, it operates with a concise set of commands, eliminating the need to learn a new language. Therefore it provides the flexibility to choose any editor while utilizing familiar languages like HTML, CSS, and JavaScript when building apps, freeing developers from the constraints of official tools.</div>
</div>

<h3 id="heading-cordova-app-architecture">Cordova App Architecture</h3>
<p>Cordova's key feature lies in enhancing the native web view component on each platform, establishing a link through plugins to provide access to the device's native capabilities for the app via JavaScript. In essence, Cordova acts as a bridge, seamlessly integrating web technologies into native app development, making it a versatile solution for cross-platform hybrid applications.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709355033836/0b9591ed-ff6c-47f9-bc0f-e79cba9778a2.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-cordovas-strengths">Cordova's Strengths</h3>
<ol>
<li><p><strong>Cross-Platform Efficiency:</strong></p>
<ul>
<li>It enables mobile developers to target multiple platforms with a single code base using HTML, CSS, and JavaScript.</li>
</ul>
</li>
<li><p><strong>Web App Conversion:</strong></p>
<ul>
<li><p>Ideal for web developers looking to convert existing web apps into native apps, leveraging existing code for diverse platforms supported by Cordova.</p>
</li>
<li><p>Provides the opportunity to showcase apps in platform app stores without the need to learn complex platform-specific tools.</p>
</li>
</ul>
</li>
<li><p><strong>Extensive Platform Support:</strong></p>
<ul>
<li><p>Supports a wide range of platforms, allowing developers to write a single app for various phones and tablets.</p>
</li>
<li><p>Check the Cordova platform support page for a comprehensive list of supported platforms and features.</p>
</li>
</ul>
</li>
<li><p><strong>Versatile Build Tool:</strong></p>
<ul>
<li><p>Acts as a robust build tool, taking web applications as input and producing native apps as output.</p>
</li>
<li><p>Specifies required device capabilities and integrates plugins to enable these capabilities, offering flexibility in app development.</p>
</li>
</ul>
</li>
<li><p><strong>Mature and Established:</strong></p>
<ul>
<li><p>One of the oldest cross-platform tools, Cordova benefits from extensive bug fixing and has served as the foundation for other frameworks.</p>
</li>
<li><p>Boasts a large development community and extensive documentation, making it a reliable and well-supported choice.</p>
</li>
</ul>
</li>
<li><p><strong>Language Familiarity:</strong></p>
<ul>
<li><p>Developers can leverage HTML, CSS, and JavaScript, languages they likely already know, to build Cordova apps.</p>
</li>
<li><p>Eliminates the need to learn additional programming languages like Java, C#, Objective-C, or Swift.</p>
</li>
</ul>
</li>
<li><p><strong>Editor and IDE Independence:</strong></p>
<ul>
<li><p>Cordova doesn't impose a specific editor or integrated development environment (IDE), offering flexibility in choosing development tools.</p>
</li>
<li><p>Unlike IDEs, Cordova focuses on the build process and doesn't include features like debugging tools or project-based organization.</p>
</li>
</ul>
</li>
<li><p><strong>Ease of Learning:</strong></p>
<ul>
<li><p>Cordova's simplicity lies in its short list of commands, making it easy to learn and use for developers familiar with web technologies.</p>
</li>
<li><p>Eliminates the need to invest time in learning new programming languages or complex IDEs.</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-progressive-web-applications-pwas">Progressive Web Applications (PWAs)</h2>
<h3 id="heading-what-are-pwas">What are PWAs</h3>
<p>Today's modern browsers have overcome the limitations of native apps and hybrid frameworks by incorporating features that address various shortcomings. These browsers now support offline content loading, standardized technologies like responsive design, and web push notifications. Notably, they allow users to add web apps to their home screens on mobile devices, providing an app-like experience. Progressive web apps (PWAs) take advantage of these advancements, functioning as websites leveraging new web technologies such as web app manifests and JavaScript service workers. PWAs prioritize responsiveness, ensuring they adapt seamlessly to diverse platforms and screen sizes like any other responsive website.</p>
<h3 id="heading-pwas-checklist">PWAs Checklist</h3>
<ol>
<li><p><strong>Responsive:</strong> Prioritize responsiveness to ensure the PWA seamlessly adapts to various platforms and screen sizes.</p>
</li>
<li><p><strong>Secure:</strong> Ensure the PWA is served over HTTPS to meet security requirements and protect user data.</p>
</li>
<li><p><strong>Available Offline:</strong> implementing effective caching strategies to allow offline content loading and offline resource caching.</p>
</li>
<li><p><strong>Installable:</strong> Enable users to install the PWA on their home screens, providing a more app-like experience.</p>
</li>
<li><p><strong>Cross-Browser Compatibility:</strong> Test and ensure that the PWA works seamlessly in multiple browsers that support the PWA specification.</p>
</li>
</ol>
<p>More information on the PWA Checklist can be found on the <a target="_blank" href="https://web.dev/articles/pwa-checklist">PWA Website.</a></p>
<h3 id="heading-pwas-strengths">PWAs' Strengths</h3>
<ol>
<li><p><strong>Data-Friendly:</strong> Highly efficient in terms of data usage, often requiring a fraction of the size compared to traditional native apps. Users can interact with the app without the need for a prior installation, and they can decide to install it later.</p>
</li>
<li><p><strong>Easy Updates:</strong> By updating files on servers, new features or content can be seamlessly integrated. Properly configured PWAs automatically download and install updates on the next launch.</p>
</li>
<li><p><strong>Simple Sharing:</strong> Sharing PWAs is exceptionally straightforward since they are essentially URLs. These links can be embedded in various platforms, including social media and emails. The process does not involve app store interactions for updates or sharing.</p>
</li>
<li><p><strong>No App Store Dependency:</strong> Operate independently of app stores, providing a hassle-free environment for updates and sharing. Developers have more control over the distribution process, contributing to a more streamlined experience.</p>
</li>
<li><p><strong>Push Notification Support:</strong> PWAs include support for push notifications, enhancing user engagement by allowing developers to keep users informed and involved in app updates and relevant content.</p>
</li>
<li><p><strong>URL-Based Accessibility:</strong> As PWAs are accessed through URLs, users can interact with them directly without navigating through app stores. This simplicity makes PWAs easily accessible and shareable across various platforms.</p>
</li>
</ol>
<h2 id="heading-react-native">React Native</h2>
<h3 id="heading-what-is-react-native">What is React Native?</h3>
<p>React Native is an open-source cross-platform Native app framework rooted in Facebook's widely used React JavaScript library. This framework emerged as a response to the dissatisfaction of Facebook engineers with the performance of their hybrid mobile app.</p>
<blockquote>
<p>React Native app development involves initiation and compilation through a command line interface, resulting in Native Android and iOS apps crafted using JavaScript, ECMAScript, and JSX.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709398559936/3db1bf0f-be75-4cdf-9028-5b8a546411ae.png" alt class="image--center mx-auto" /></p>
</blockquote>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Unlike traditional web development, React Native employs React Native components instead of HTML elements, which are then compiled into platform-specific UI components through bridges connecting the JavaScript code and the underlying platform.</div>
</div>

<h3 id="heading-react-native-app-architecture">React Native App Architecture</h3>
<p>Developers predominantly use a combination of JavaScript, ECMAScript, and JSX, a language allowing XML embedding within JavaScript, to write React Native apps. The framework excels in seamlessly compiling code into Native Android and iOS apps, offering indistinguishable performance from apps crafted with platform-specific tools. This approach integrates business logic in JavaScript with UI code in JSX or ECMAScript, enabling comprehensive app development from a unified code base. Additionally, React Native facilitates the incorporation of platform-specific code if needed, allowing developers to extend and modify the resulting Native app in Android Studio or Xcode.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709398530950/7aea9108-96da-4b31-8ee0-4ce61fe7b655.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-react-natives-strengths">React Native's Strengths</h3>
<ol>
<li><p><strong>Speed and Performance:</strong> React Native excels in delivering fast and efficient apps by generating Native applications rather than relying on hybrid approaches, eliminating the performance lag associated with hybrid apps.</p>
</li>
<li><p><strong>User Interface Development:</strong> At its core, React Native serves as a powerful user interface development tool. It allows developers to use React Native views instead of HTML or platform-specific UI components, resulting in apps that utilize the same UI components as regular iOS and Android apps.</p>
</li>
<li><p><strong>Language Flexibility:</strong> React Native empowers developers to use JavaScript, specifically ECMAScript (ES6), for building applications. ES6, the official standard, enhances the capabilities of JavaScript and contributes to the development of robust React Native apps.</p>
</li>
<li><p><strong>Testing Efficiency:</strong> With the Expo SDK, React Native facilitates efficient app testing on devices without the need for complex tools like Android Studio or Xcode, streamlining the testing process and enhancing overall development efficiency.</p>
</li>
<li><p><strong>Single Code Base for Multiple Platforms:</strong> Developers can maintain a single code base for both Android and iOS, as React Native handles platform differences, allowing a focus on app functionality rather than intricate platform-specific details.</p>
</li>
<li><p><strong>Hot Reload Feature:</strong> This feature enables developers to repackage and update launched applications without losing their state, providing a quick and efficient way to test and implement new features.</p>
</li>
<li><p><strong>Integration of Native Code:</strong> smoothly integrates with Native code, allowing developers to combine it with components written in Objective-C, Swift, or Java, offering flexibility in app optimization.</p>
</li>
<li><p><strong>Active Development Community:</strong> Leveraging the React JavaScript library's popularity, React Native benefits from a large and active development community, ensuring access to extensive resources and support for developers.</p>
</li>
</ol>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The natural place for React Native to excel is in the speed of the deployed app. Hybrid apps run code inside a platform-specific web component. This layered approach makes hybrid apps slower than your Native counterparts. React Native produces Native apps so they are as fast as any other Native app deployed on the device.</div>
</div>

<hr />
<p>This article has been able to explore 3 of the most popular cross-platform frameworks: Cordova, PWAs and React Native. I'll explore the other 3: Xamarin, Flutter and KMM in the next article: <a target="_blank" href="https://siro.hashnode.dev/cross-platform-development-in-2024-3">Cross-Platform Development in 2024 3/3</a>. So see you in the next one.</p>
]]></content:encoded></item></channel></rss>