<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kumar Harsh</title>
    <description>The latest articles on DEV Community by Kumar Harsh (@kumarharsh).</description>
    <link>https://dev.to/kumarharsh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2564857%2Ff9e5b218-09d6-4931-a37a-6a97dd39e9e7.png</url>
      <title>DEV Community: Kumar Harsh</title>
      <link>https://dev.to/kumarharsh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kumarharsh"/>
    <language>en</language>
    <item>
      <title>The Complete Guide to Flutter BLoC State Management</title>
      <dc:creator>Kumar Harsh</dc:creator>
      <pubDate>Wed, 18 Mar 2026 17:17:50 +0000</pubDate>
      <link>https://dev.to/kumarharsh/the-complete-guide-to-flutter-bloc-state-management-3lp9</link>
      <guid>https://dev.to/kumarharsh/the-complete-guide-to-flutter-bloc-state-management-3lp9</guid>
      <description>&lt;p&gt;&lt;code&gt;setState&lt;/code&gt; works fine when your Flutter app has three screens. When it has thirty, things change. Shared user sessions, paginated lists, real-time socket data, and concurrent API calls turn &lt;code&gt;setState&lt;/code&gt; into an obstacle. You end up passing callbacks five widgets deep. Your &lt;code&gt;Provider&lt;/code&gt; notifiers accumulate business logic. Repository methods get called from inside &lt;code&gt;build()&lt;/code&gt;. The app runs, but nobody on the team can easily explain what triggered a UI rebuild.&lt;/p&gt;

&lt;p&gt;That's the state management inflection point. Teams either adopt a disciplined architecture at that moment, or spend the next 18 months debugging race conditions and unintended rebuilds.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://bloclibrary.dev/" rel="noopener noreferrer"&gt;BLoC (Business Logic Component) pattern&lt;/a&gt; is where serious teams land. Google's own Flutter team uses it internally. &lt;a href="https://nubank.com.br/en/" rel="noopener noreferrer"&gt;Nubank&lt;/a&gt;, one of the world's largest digital banks with 90 million customers, built its app on it. &lt;a href="https://flutter.dev/showcase/bmw" rel="noopener noreferrer"&gt;BMW&lt;/a&gt; runs it in production. The &lt;a href="https://pub.dev/packages/flutter_bloc" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_bloc&lt;/code&gt;&lt;/a&gt; package has logged over 1.4 million downloads on pub.dev. That's not hype. That's validation at scale.&lt;/p&gt;

&lt;p&gt;This guide covers how BLoC works, how to implement it with modern Dart 3.x features, and why its predictability matters when you're shipping over-the-air patches with tools like &lt;a href="https://shorebird.dev/" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Concepts: Events, States, and Streams
&lt;/h2&gt;

&lt;p&gt;Before getting into code, it helps to understand the three moving parts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;strong&gt;Event&lt;/strong&gt; is an intention. The user taps "Fetch Weather," and your app dispatches an event. Events don't carry logic. They carry data. They say "something happened" and pass along whatever context is needed to handle it.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;State&lt;/strong&gt; is a snapshot of what the UI should show. Loading, success, failure, plus any data needed to render.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;BLoC&lt;/strong&gt; receives events, executes business logic (validation, API calls, caching), and emits new states through a Dart &lt;code&gt;Stream&lt;/code&gt;. Widgets listen and rebuild when state changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNplUV1LwzAU_SvhPil0s03btQsy0OlDYYJMhqDxIWvv2kLblCRV57b_brJNfTAPybm55yPk7iCXBQKDTSM_8kooQxZL3hG7Vtkrh1VGFmKL6nqtZhfPdVGiueTwRkaj2Z5DUetemLxCTe7fsTMc9uR2IecnA4fORGxro8mTEQYdZ5X9Y-SiabTrLbGXNtgdujZSbckVuXnMbOhJ4xpnjUIzqE6TQhjxFw0elKougBk1oActqla4EnbOgIOpsLWvYBauhbaIdwer6UX3ImX7I1NyKCtgG9FoWw29zcC7WpRKtL-3CrsC1VwOnQFG46MHsB18AkuDcRrEkU9pkNqNBh5sgYVRPJ5OIj-YBuE0Tif04MHXMdQfR6ml-3GahNQPaZJMPMDCfcDDaUDHOR2-AUoWhIE" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNplUV1LwzAU_SvhPil0s03btQsy0OlDYYJMhqDxIWvv2kLblCRV57b_brJNfTAPybm55yPk7iCXBQKDTSM_8kooQxZL3hG7Vtkrh1VGFmKL6nqtZhfPdVGiueTwRkaj2Z5DUetemLxCTe7fsTMc9uR2IecnA4fORGxro8mTEQYdZ5X9Y-SiabTrLbGXNtgdujZSbckVuXnMbOhJ4xpnjUIzqE6TQhjxFw0elKougBk1oActqla4EnbOgIOpsLWvYBauhbaIdwer6UX3ImX7I1NyKCtgG9FoWw29zcC7WpRKtL-3CrsC1VwOnQFG46MHsB18AkuDcRrEkU9pkNqNBh5sgYVRPJ5OIj-YBuE0Tif04MHXMdQfR6ml-3GahNQPaZJMPMDCfcDDaUDHOR2-AUoWhIE%3Ftype%3Dpng" width="718" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dart 3 Makes BLoC Better
&lt;/h2&gt;

&lt;p&gt;Dart 3 gives you &lt;code&gt;sealed&lt;/code&gt; classes and exhaustive &lt;code&gt;switch&lt;/code&gt; expressions. With a &lt;code&gt;sealed&lt;/code&gt; base state, the compiler knows every possible subtype, so your UI &lt;code&gt;switch&lt;/code&gt; becomes a compile-time guarantee. Add a new state and forget to handle it, and the build fails.&lt;/p&gt;

&lt;p&gt;That's one of the biggest practical improvements BLoC codebases have seen in years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Weather App with BLoC
&lt;/h2&gt;

&lt;p&gt;Let's see how this works in practice by building a weather app.&lt;/p&gt;

&lt;p&gt;Start by creating a new Flutter project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter create my_new_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, if you want over-the-air update support from day one, &lt;a href="https://docs.shorebird.dev/getting-started/" rel="noopener noreferrer"&gt;install the Shorebird CLI&lt;/a&gt; and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shorebird create my_new_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting Up: Packages
&lt;/h3&gt;

&lt;p&gt;Add the following to your &lt;code&gt;pubspec.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter_bloc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^9.1.0&lt;/span&gt;
  &lt;span class="na"&gt;equatable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^2.0.5&lt;/span&gt;
  &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^1.2.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flutter_bloc&lt;/code&gt; wires streams into the widget tree with &lt;code&gt;BlocProvider&lt;/code&gt; and &lt;code&gt;BlocBuilder&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;equatable&lt;/code&gt; gives your events, states, and models value equality.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;http&lt;/code&gt; is used by the repository to call Open-Meteo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step-by-Step Implementation: Weather Fetch with Refresh
&lt;/h3&gt;

&lt;p&gt;This implementation covers two user actions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch shows a spinner and replaces the current UI state.&lt;/li&gt;
&lt;li&gt;Refresh updates silently, so you don't flash a loading indicator over existing data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 1: Model the Domain
&lt;/h4&gt;

&lt;p&gt;A small, UI-friendly model keeps state rendering predictable. It also decouples your UI from the raw API response shape.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/features/weather/models/weather.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:equatable/equatable.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// A minimal, UI-friendly weather model for the BLoC tutorial.&lt;/span&gt;
&lt;span class="c1"&gt;///&lt;/span&gt;
&lt;span class="c1"&gt;/// Notes&lt;/span&gt;
&lt;span class="c1"&gt;/// - `tempC` is Celsius to keep formatting predictable in the UI.&lt;/span&gt;
&lt;span class="c1"&gt;/// - `conditionCode` is the raw weather code from the API (useful for icons).&lt;/span&gt;
&lt;span class="c1"&gt;/// - `fetchedAt` helps you show "updated X min ago" and supports refresh logic.&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Weather&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Equatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tempC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;conditionCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;fetchedAt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Weather&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tempC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;conditionCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetchedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;/// Open-Meteo returns current conditions with numeric weather codes.&lt;/span&gt;
  &lt;span class="c1"&gt;/// We fetch the human-readable condition name ourselves.&lt;/span&gt;
  &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;Weather&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromOpenMeteo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'current'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FormatException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Missing "current" in weather response.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'temperature_2m'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'weather_code'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'time'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FormatException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Weather response missing required fields.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;fetchedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tryParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fetchedAt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FormatException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Invalid "time" in weather response.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;codeInt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toInt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;city:&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;tempC:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toDouble&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nl"&gt;conditionCode:&lt;/span&gt; &lt;span class="n"&gt;codeInt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;condition:&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;codeInt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;fetchedAt:&lt;/span&gt; &lt;span class="n"&gt;fetchedAt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toLocal&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tempC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conditionCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetchedAt&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;/// Weather condition mapping for Open-Meteo weather codes.&lt;/span&gt;
&lt;span class="c1"&gt;/// Source: Open-Meteo weather codes list.&lt;/span&gt;
&lt;span class="kt"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Clear'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;mainlyClear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Mainly clear'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;partlyCloudy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Partly cloudy'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;overcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Overcast'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;fog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Fog'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;depositingRimeFog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Depositing rime fog'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;drizzleLight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Light drizzle'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;drizzleModerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Moderate drizzle'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;drizzleDense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Dense drizzle'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;freezingDrizzleLight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Light freezing drizzle'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;freezingDrizzleDense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Dense freezing drizzle'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;rainSlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Slight rain'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;rainModerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Moderate rain'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;rainHeavy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Heavy rain'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;freezingRainLight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Light freezing rain'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;freezingRainHeavy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Heavy freezing rain'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;snowSlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Slight snow'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;snowModerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Moderate snow'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;snowHeavy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Heavy snow'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;snowGrains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Snow grains'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;rainShowersSlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Slight rain showers'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;rainShowersModerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Moderate rain showers'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;rainShowersViolent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Violent rain showers'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;snowShowersSlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Slight snow showers'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;snowShowersHeavy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Heavy snow showers'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;thunderstormSlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Thunderstorm'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;thunderstormModerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Thunderstorm'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;thunderstormSlightHail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Thunderstorm with slight hail'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;thunderstormHeavyHail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Thunderstorm with heavy hail'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Unknown'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt; &lt;span class="n"&gt;fromCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mainlyClear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;partlyCloudy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;overcast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;45&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;48&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;depositingRimeFog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;51&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;drizzleLight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;53&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;drizzleModerate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;55&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;drizzleDense&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;56&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;freezingDrizzleLight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;57&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;freezingDrizzleDense&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;61&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rainSlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;63&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rainModerate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;65&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rainHeavy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;66&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;freezingRainLight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;67&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;freezingRainHeavy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;71&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;snowSlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;73&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;snowModerate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;75&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;snowHeavy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;77&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;snowGrains&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rainShowersSlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;81&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rainShowersModerate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;82&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rainShowersViolent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;85&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;snowShowersSlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;86&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;snowShowersHeavy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;95&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thunderstormSlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;96&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thunderstormSlightHail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;99&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thunderstormHeavyHail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherCondition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Equatable&lt;/code&gt; is key here. Identical values compare equal, which prevents subtle UI churn from unnecessary rebuilds.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Build a Repository That Owns HTTP and Parsing
&lt;/h4&gt;

&lt;p&gt;The repository has one job: take a city string and return a &lt;code&gt;Weather&lt;/code&gt; model, or throw a meaningful error.&lt;/p&gt;

&lt;p&gt;It makes two network calls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Geocode the city name to coordinates&lt;/li&gt;
&lt;li&gt;Fetch current conditions for those coordinates
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/features/weather/repositories/weather_repository.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:convert'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:io'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:http/http.dart'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../models/weather.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// Repository that:&lt;/span&gt;
&lt;span class="c1"&gt;/// 1) Geocodes a city name -&amp;gt; lat/long&lt;/span&gt;
&lt;span class="c1"&gt;/// 2) Fetches current weather for lat/long&lt;/span&gt;
&lt;span class="c1"&gt;///&lt;/span&gt;
&lt;span class="c1"&gt;/// Uses Open-Meteo:&lt;/span&gt;
&lt;span class="c1"&gt;/// - Geocoding: https://geocoding-api.open-meteo.com/v1/search&lt;/span&gt;
&lt;span class="c1"&gt;/// - Forecast:   https://api.open-meteo.com/v1/forecast&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Client&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;WeatherRepository&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Weather&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FormatException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'City cannot be empty.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;coords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_geocodeCity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_fetchCurrentWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Weather&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromOpenMeteo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;city:&lt;/span&gt; &lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;json:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_GeoResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_geocodeCity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'geocoding-api.open-meteo.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;'/v1/search'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;{&lt;/span&gt;
        &lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;'count'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;'language'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;'format'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;headers:&lt;/span&gt; &lt;span class="n"&gt;_headers&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;HttpException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Geocoding failed (HTTP &lt;/span&gt;&lt;span class="si"&gt;${res.statusCode}&lt;/span&gt;&lt;span class="s"&gt;).'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FormatException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Invalid geocoding response.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'results'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'No results found for "&lt;/span&gt;&lt;span class="si"&gt;$city&lt;/span&gt;&lt;span class="s"&gt;".'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;first&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FormatException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Invalid geocoding result.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'latitude'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'longitude'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;lon&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FormatException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Geocoding result missing fields.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;admin1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'admin1'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'country'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin1&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;admin1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_GeoResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;latitude:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toDouble&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nl"&gt;longitude:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toDouble&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nl"&gt;displayName:&lt;/span&gt; &lt;span class="n"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_fetchCurrentWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'api.open-meteo.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/v1/forecast'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;{&lt;/span&gt;
          &lt;span class="s"&gt;'latitude'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="s"&gt;'longitude'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="s"&gt;'current'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'temperature_2m,weather_code'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;'temperature_unit'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'celsius'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;'timezone'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'auto'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;headers:&lt;/span&gt; &lt;span class="n"&gt;_headers&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;HttpException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Weather fetch failed (HTTP &lt;/span&gt;&lt;span class="si"&gt;${res.statusCode}&lt;/span&gt;&lt;span class="s"&gt;).'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;FormatException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Invalid weather response.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'Accept'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_GeoResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_GeoResult&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation pays off later. If you swap Open-Meteo for a paid provider, add caching, or move from REST to GraphQL, your BLoC and UI code stays unchanged. The repository absorbs the change.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Define Events
&lt;/h4&gt;

&lt;p&gt;Define two events, both carrying the city string. That keeps the public BLoC API small and makes the event history readable in logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/features/weather/bloc/weather_event.dart&lt;/span&gt;
&lt;span class="kn"&gt;part of&lt;/span&gt; &lt;span class="s"&gt;'weather_bloc.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherEvent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Equatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherEvent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherFetchRequested&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;WeatherEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherFetchRequested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherRefreshRequested&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;WeatherEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherRefreshRequested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Define States
&lt;/h4&gt;

&lt;p&gt;This is the classic "initial, loading, success, failure" set. It's predictable, testable, and maps cleanly to UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/features/weather/bloc/weather_state.dart&lt;/span&gt;
&lt;span class="kn"&gt;part of&lt;/span&gt; &lt;span class="s"&gt;'weather_bloc.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Equatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherInitial&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;WeatherState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherInitial&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherLoading&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;WeatherState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherLoading&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherSuccess&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;WeatherState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Weather&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherFailure&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;WeatherState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 5: Implement the BLoC
&lt;/h4&gt;

&lt;p&gt;This BLoC uses the modern &lt;code&gt;on&amp;lt;Event&amp;gt;(handler)&lt;/code&gt; style. Fetch shows a loading state. Refresh tries to update without forcing a spinner, so the UI stays stable when the user is already looking at data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/features/weather/bloc/weather_bloc.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_bloc/flutter_bloc.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:equatable/equatable.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../models/weather.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../repositories/weather_repository.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;part&lt;/span&gt; &lt;span class="s"&gt;'weather_event.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;part&lt;/span&gt; &lt;span class="s"&gt;'weather_state.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherBloc&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Bloc&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WeatherState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;WeatherRepository&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;WeatherBloc&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;WeatherRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherInitial&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;on&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherFetchRequested&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;_onFetchRequested&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;on&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherRefreshRequested&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;_onRefreshRequested&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_onFetchRequested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;WeatherFetchRequested&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Emitter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherLoading&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetchWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WeatherSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WeatherFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_onRefreshRequested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;WeatherRefreshRequested&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Emitter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Refresh silently: don't replace existing data with a spinner&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetchWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WeatherSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WeatherFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This code emits &lt;code&gt;WeatherFailure&lt;/code&gt; on refresh errors. In some apps you might keep the previous &lt;code&gt;WeatherSuccess&lt;/code&gt; state and show a non-blocking toast instead. That's a product decision, not an architecture limitation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Step 6: Wire It Into the UI
&lt;/h4&gt;

&lt;p&gt;This tutorial uses a two-widget split.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WeatherPage&lt;/code&gt; sets up the feature scope and dependency injection.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WeatherView&lt;/code&gt; owns the &lt;code&gt;TextEditingController&lt;/code&gt; and renders states.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;WeatherPage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notice what it does not do. It does not construct the repository. It reads it from the tree, then constructs the BLoC with it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/features/weather/view/weather_page.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_bloc/flutter_bloc.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../bloc/weather_bloc.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../repositories/weather_repository.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'weather_view.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherPage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BlocProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;create:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherBloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;repository:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()),&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherView&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That small pattern scales well. Your feature doesn't need to care whether the repository is real, cached, mocked, or decorated for analytics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WeatherView&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This view uses Dart 3 pattern matching in the UI. Each state maps to a clear piece of output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/features/weather/view/weather_view.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_bloc/flutter_bloc.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../bloc/weather_bloc.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherView&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherView&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_WeatherViewState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_WeatherViewState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;_controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextEditingController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'London'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Weather BLoC'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;controller:&lt;/span&gt; &lt;span class="n"&gt;_controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;decoration:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;InputDecoration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nl"&gt;labelText:&lt;/span&gt; &lt;span class="s"&gt;'City'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nl"&gt;border:&lt;/span&gt; &lt;span class="n"&gt;OutlineInputBorder&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
              &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherBloc&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="n"&gt;WeatherFetchRequested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Fetch Weather'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;BlocBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherBloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WeatherState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="n"&gt;WeatherInitial&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;'Enter a city and fetch weather'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="n"&gt;WeatherLoading&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                  &lt;span class="n"&gt;WeatherSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                      &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;textTheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;headlineMedium&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="p"&gt;),&lt;/span&gt;
                      &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${weather.tempC.toStringAsFixed(1)}&lt;/span&gt;&lt;span class="s"&gt; °C'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;textTheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;displaySmall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="p"&gt;),&lt;/span&gt;
                      &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;],&lt;/span&gt;
                  &lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="n"&gt;WeatherFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;red&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want a refresh button in the UI, you already have the event. Add an &lt;code&gt;IconButton&lt;/code&gt; in the &lt;code&gt;AppBar&lt;/code&gt; that dispatches &lt;code&gt;WeatherRefreshRequested(_controller.text)&lt;/code&gt; and you'll get the silent refresh behavior from the BLoC.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 7: Compose the App in main.dart
&lt;/h4&gt;

&lt;p&gt;This app uses &lt;code&gt;RepositoryProvider&lt;/code&gt; at the root so any feature can read the repository. It also registers a global &lt;code&gt;BlocObserver&lt;/code&gt; so state transitions show up in logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/main.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_bloc/flutter_bloc.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'features/weather/repositories/weather_repository.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'features/weather/view/weather_page.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Bloc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AppBlocObserver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RepositoryProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;create:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WeatherRepository&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;debugShowCheckedModeBanner:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="s"&gt;'Weather BLoC Demo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;colorScheme:&lt;/span&gt; &lt;span class="n"&gt;ColorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;seedColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;useMaterial3:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;home:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WeatherPage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;/// Optional but highly recommended in real apps.&lt;/span&gt;
&lt;span class="c1"&gt;/// Logs all BLoC state transitions globally.&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppBlocObserver&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;BlocObserver&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BlocBase&lt;/span&gt; &lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Change&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'[&lt;/span&gt;&lt;span class="si"&gt;${bloc.runtimeType}&lt;/span&gt;&lt;span class="s"&gt;] '&lt;/span&gt;
      &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${change.currentState.runtimeType}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; '&lt;/span&gt;
      &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${change.nextState.runtimeType}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BlocBase&lt;/span&gt; &lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StackTrace&lt;/span&gt; &lt;span class="n"&gt;stackTrace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'[&lt;/span&gt;&lt;span class="si"&gt;${bloc.runtimeType}&lt;/span&gt;&lt;span class="s"&gt;] ERROR: &lt;/span&gt;&lt;span class="si"&gt;$error&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stackTrace&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with &lt;code&gt;flutter run&lt;/code&gt; and watch it in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6r833ddrhe2eqnnk2s1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6r833ddrhe2eqnnk2s1.gif" alt="App walkthrough" width="560" height="716"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two things worth noting about this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can replace &lt;code&gt;WeatherRepository()&lt;/code&gt; with a mock in tests by swapping the provider.&lt;/li&gt;
&lt;li&gt;When a bug report arrives, transition logs tell you exactly what happened without guessing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Enterprise Teams Choose BLoC
&lt;/h2&gt;

&lt;p&gt;BLoC isn't popular because it's trendy. It sticks because it turns app behavior into something you can reason about under pressure. When the codebase grows, the team grows, and production issues show up at 2 AM, having one predictable place where "what happened" becomes "what state did we emit, and why" is the difference between a fast fix and a long debugging session.&lt;/p&gt;

&lt;p&gt;That said, the benefits go beyond debugging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Testability without an emulator.&lt;/strong&gt; &lt;code&gt;WeatherBloc&lt;/code&gt; has no dependency on Flutter rendering, so you can test it with standard unit tests. The &lt;a href="https://pub.dev/packages/bloc_test" rel="noopener noreferrer"&gt;&lt;code&gt;bloc_test&lt;/code&gt;&lt;/a&gt; package makes those tests read like specs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traceability with BlocObserver.&lt;/strong&gt; A &lt;code&gt;BlocObserver&lt;/code&gt; gives you a global lens into your app's behavior. When state transitions are visible, debugging becomes less like archaeology.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns at team scale.&lt;/strong&gt; The UI consumes states. It doesn't fetch or parse data. The repository handles IO and parsing. The BLoC handles orchestration. That division is what keeps larger teams from stepping on each other.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  BLoC + Shorebird: Safer OTA Updates
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://shorebird.dev/" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt; gives Flutter teams code push, so critical fixes can ship without waiting on store review. That only stays safe if the code you're patching is predictable.&lt;/p&gt;

&lt;p&gt;BLoC helps here by concentrating behavior in small, isolated units. If the weather refresh behavior is wrong, the fix is likely in &lt;code&gt;WeatherBloc&lt;/code&gt; or &lt;code&gt;WeatherRepository&lt;/code&gt;, not scattered across widget lifecycle methods. Smaller change surface, smaller blast radius.&lt;/p&gt;

&lt;p&gt;Shorebird solves delivery. BLoC reduces uncertainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices and Common Pitfalls
&lt;/h2&gt;

&lt;p&gt;A few things I'd recommend keeping in mind when working with BLoC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep navigation out of BLoCs. Emit a state and handle navigation in the UI layer.&lt;/li&gt;
&lt;li&gt;Prefer immutable state objects. Your states already use &lt;code&gt;final&lt;/code&gt; fields and &lt;code&gt;const&lt;/code&gt; constructors. Keep that discipline.&lt;/li&gt;
&lt;li&gt;Keep each BLoC single-purpose. If a BLoC starts owning unrelated domains, it becomes a bottleneck.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Cubit&lt;/code&gt; when you don't need an event stream. For many UI-only toggles, that simplicity is worth it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;BLoC asks for more structure up front. The payoff is clarity under pressure. When async calls overlap, when screens multiply, when hotfixes need to ship fast, a predictable "event in, state out" architecture is what keeps a Flutter app maintainable.&lt;/p&gt;

&lt;p&gt;If you're building something that will grow beyond one developer and one quarter, BLoC is worth the investment. Once it's in place, a tool like &lt;a href="https://shorebird.dev/" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt; makes delivery match the architecture: fast updates without crossing your fingers.&lt;/p&gt;

&lt;p&gt;I'm curious how others have approached this. If you've used BLoC in production, what's the one structural decision you wish you'd made earlier? Drop a comment below or share your experience. I read them all.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>programming</category>
      <category>development</category>
    </item>
    <item>
      <title>Flutter Production Setup for Every Platform in 2026</title>
      <dc:creator>Kumar Harsh</dc:creator>
      <pubDate>Wed, 04 Mar 2026 19:03:30 +0000</pubDate>
      <link>https://dev.to/kumarharsh/flutter-production-setup-for-every-platform-in-2026-396a</link>
      <guid>https://dev.to/kumarharsh/flutter-production-setup-for-every-platform-in-2026-396a</guid>
      <description>&lt;p&gt;Flutter 3.41 (February 2026) is the latest stable release, bundled with Dart 3.11, and getting the installation right from day one makes a real difference. Broken PATH variables, missing Android SDK command-line tools, and CocoaPods failures on Apple Silicon are the three problems that consume hours of developer time every week. None of them should catch you by surprise.&lt;/p&gt;

&lt;p&gt;This guide covers the exact commands and configuration for macOS, Windows, and Linux, with a focus on the professional workflow used in production teams. It also covers integrating &lt;a href="https://shorebird.dev/" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt; for over-the-air updates from the start of a project, not as an afterthought. macOS remains the only platform that can target all six Flutter output formats (iOS, Android, web, macOS desktop, Windows via cross-compilation, and Linux), which is why it's the dominant choice for mobile development shops.&lt;/p&gt;




&lt;h2&gt;
  
  
  System requirements across all platforms
&lt;/h2&gt;

&lt;p&gt;Flutter's SDK weighs roughly 2.8 GB on disk, but a realistic production setup, including Android SDK, emulator images, and an IDE, requires 10 GB or more of free space. The &lt;a href="https://docs.flutter.dev/get-started/install" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; don't specify a hard RAM floor, but community consensus and practical experience put the minimum at 8 GB, with 16 GB strongly recommended when running Android emulators or iOS simulators alongside an IDE.&lt;/p&gt;

&lt;p&gt;Every platform requires Git 2.x as a prerequisite. On macOS, Git ships with &lt;a href="https://developer.apple.com/xcode/resources/" rel="noopener noreferrer"&gt;Xcode Command-Line Tools&lt;/a&gt;. On Windows, install &lt;a href="https://git-scm.com/downloads/win" rel="noopener noreferrer"&gt;Git for Windows&lt;/a&gt;. On Ubuntu, &lt;code&gt;sudo apt-get install git&lt;/code&gt; handles it. Verify with &lt;code&gt;git --version&lt;/code&gt; before proceeding.&lt;/p&gt;

&lt;p&gt;Here's a quick rundown of the requirements for each platform:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;macOS&lt;/th&gt;
&lt;th&gt;Windows&lt;/th&gt;
&lt;th&gt;Linux (Ubuntu LTS)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OS version&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;macOS 10.15 Catalina through macOS 26 Tahoe&lt;/td&gt;
&lt;td&gt;64-bit Windows 10 or 11&lt;/td&gt;
&lt;td&gt;64-bit Debian-based or Fedora&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Disk space&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 GB+ recommended&lt;/td&gt;
&lt;td&gt;10 GB+ recommended&lt;/td&gt;
&lt;td&gt;10 GB+ recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8 GB min, 16 GB ideal&lt;/td&gt;
&lt;td&gt;8 GB min, 16 GB ideal&lt;/td&gt;
&lt;td&gt;8 GB recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Git&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Via Xcode CLI tools&lt;/td&gt;
&lt;td&gt;Git for Windows&lt;/td&gt;
&lt;td&gt;&lt;code&gt;apt-get install git&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Additional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Xcode, CocoaPods&lt;/td&gt;
&lt;td&gt;Visual Studio 2022+ (for desktop)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;unzip&lt;/code&gt;, &lt;code&gt;xz-utils&lt;/code&gt;, &lt;code&gt;zip&lt;/code&gt;, &lt;code&gt;libglu1-mesa&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Flutter bundles the &lt;a href="https://dart.dev/get-dart" rel="noopener noreferrer"&gt;Dart SDK&lt;/a&gt;, so no separate Dart installation is needed.&lt;/p&gt;

&lt;p&gt;One thing worth noting: the documentation site currently references Flutter 3.38.6 in many pages, while the actual latest stable is 3.41. Always use the stable channel for production work. Four stable releases are &lt;a href="https://blog.flutter.dev/whats-new-in-flutter-3-41-302ec140e632#:~:text=For%202026%2C%20we%20plan%20to%20release%20four%20stable%20releases" rel="noopener noreferrer"&gt;planned for 2026&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  macOS installation
&lt;/h2&gt;

&lt;p&gt;macOS setup is the most complex of the three platforms because it's the only one supporting both iOS and Android development simultaneously. The single biggest architectural decision is whether you're on Apple Silicon (M1/M2/M3/M4) or Intel, because &lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt; install paths, Ruby environments, and emulator image choices all differ between the two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing the Flutter SDK
&lt;/h3&gt;

&lt;p&gt;The fastest method is Homebrew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; flutter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Homebrew automatically selects the correct architecture. On Apple Silicon, Homebrew lives at &lt;code&gt;/opt/homebrew/&lt;/code&gt;. On Intel, it's at &lt;code&gt;/usr/local/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you prefer manual installation, download the architecture-specific zip from the &lt;a href="https://docs.flutter.dev/install/archive#stable-channel" rel="noopener noreferrer"&gt;Flutter SDK archive&lt;/a&gt;. Apple Silicon uses the &lt;code&gt;flutter_macos_arm64_*.zip&lt;/code&gt; bundle and Intel uses &lt;code&gt;flutter_macos_*.zip&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once downloaded, extract the zip to a development directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/develop
unzip ~/Downloads/flutter_macos_arm64_3.41.0-stable.zip &lt;span class="nt"&gt;-d&lt;/span&gt; ~/develop/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For manual installs, add Flutter to your PATH. Since macOS defaults to zsh (since Catalina), edit &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/develop/flutter/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For bash users, add the same line to &lt;code&gt;~/.bash_profile&lt;/code&gt;. Apple Silicon users also need Homebrew's shell environment configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;/opt/homebrew/bin/brew shellenv&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Intel users must substitute &lt;code&gt;/usr/local/bin/brew&lt;/code&gt;. You can verify your Flutter CLI installation with the &lt;code&gt;which flutter&lt;/code&gt; and &lt;code&gt;flutter --version&lt;/code&gt; commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Xcode and iOS toolchain
&lt;/h3&gt;

&lt;p&gt;Install &lt;a href="https://developer.apple.com/xcode/" rel="noopener noreferrer"&gt;Xcode&lt;/a&gt; from the Mac App Store (Flutter 3.38+ fully supports Xcode 26 and iOS 26), then run these commands in sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcode-select &lt;span class="nt"&gt;--install&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'xcode-select -s /Applications/Xcode.app/Contents/Developer &amp;amp;&amp;amp; xcodebuild -runFirstLaunch'&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;xcodebuild &lt;span class="nt"&gt;-license&lt;/span&gt; accept
xcodebuild &lt;span class="nt"&gt;-downloadPlatform&lt;/span&gt; iOS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CocoaPods: the Apple Silicon pain point
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://cocoapods.org/" rel="noopener noreferrer"&gt;CocoaPods&lt;/a&gt; remains required for Flutter plugins that use native iOS/macOS code, and it's the single most common source of build failures on Apple Silicon.&lt;/p&gt;

&lt;p&gt;Install CocoaPods via Homebrew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;cocoapods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system Ruby shipped with macOS (2.6.x) is typically too old. If the above doesn't work, install a modern Ruby first and then install CocoaPods via gem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;ruby
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
gem &lt;span class="nb"&gt;install &lt;/span&gt;cocoapods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you encounter &lt;code&gt;ffi&lt;/code&gt; gem errors on Apple Silicon, run &lt;code&gt;sudo gem install ffi&lt;/code&gt;. &lt;a href="https://support.apple.com/en-us/HT211861" rel="noopener noreferrer"&gt;Rosetta 2&lt;/a&gt; may still be needed for some edge-case components, which you can install by running &lt;code&gt;sudo softwareupdate --install-rosetta --agree-to-license&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A word of caution: never use &lt;code&gt;sudo gem install&lt;/code&gt; with the system Ruby. It can corrupt future macOS updates.&lt;/p&gt;

&lt;p&gt;A notable 2025/2026 development worth knowing about is that Swift Package Manager is now supported as an alternative to CocoaPods for Flutter plugins, documented in the &lt;a href="https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers" rel="noopener noreferrer"&gt;Flutter Swift Package Manager guide&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Windows and Linux installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Windows 10/11 setup
&lt;/h3&gt;

&lt;p&gt;After installing Git for Windows, the Flutter team recommends the VS Code quick-install path: install &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt;, add the Flutter extension, open the Command Palette (&lt;code&gt;Ctrl+Shift+P&lt;/code&gt;), type &lt;code&gt;Flutter: New Project&lt;/code&gt;, and VS Code will prompt you to download the SDK and add it to PATH automatically.&lt;/p&gt;

&lt;p&gt;For manual installation, download the latest &lt;code&gt;.zip&lt;/code&gt; from the &lt;a href="https://docs.flutter.dev/install/archive#stable-channel" rel="noopener noreferrer"&gt;Flutter SDK archive&lt;/a&gt; and extract it to a path without spaces or special characters. Never use &lt;code&gt;C:\Program Files\&lt;/code&gt;. A good choice is &lt;code&gt;%USERPROFILE%\develop\&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Expand-Archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="nx"&gt;\Downloads\flutter_windows_3.41.0-stable.zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="nx"&gt;\develop\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add Flutter to your path, configure PATH through &lt;strong&gt;System Properties → Advanced → Environment Variables&lt;/strong&gt;. Edit the &lt;code&gt;Path&lt;/code&gt; user variable and add &lt;code&gt;C:\Users\{username}\develop\flutter\bin&lt;/code&gt;. Move this entry to the top of the list, then close and reopen all terminal windows to apply the change.&lt;/p&gt;

&lt;p&gt;Three Windows-specific gotchas trip up nearly every developer.&lt;/p&gt;

&lt;p&gt;First, Windows Defender scanning Flutter's hundreds of thousands of small files causes severe performance degradation. Add exclusions for the Flutter SDK directory and the pub cache (&lt;code&gt;%LOCALAPPDATA%\Pub\Cache&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-MpExclusion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExclusionPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Users\{username}\develop\flutter"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, Developer Mode must be enabled for building Windows apps with plugins (&lt;strong&gt;Settings → Privacy &amp;amp; Security → For Developers → Developer Mode: On&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Third, for Windows desktop development, you need &lt;a href="https://visualstudio.microsoft.com/downloads/" rel="noopener noreferrer"&gt;Visual Studio 2022+&lt;/a&gt; with the "Desktop development with C++" workload. This is Visual Studio, not VS Code, and it's a common source of confusion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux (Ubuntu LTS) setup
&lt;/h3&gt;

&lt;p&gt;Start by installing the required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl git unzip xz-utils zip libglu1-mesa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The simplest method to install Flutter is via snap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;flutter &lt;span class="nt"&gt;--classic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a manual install, download the &lt;code&gt;.tar.xz&lt;/code&gt; from the &lt;a href="https://docs.flutter.dev/release/archive" rel="noopener noreferrer"&gt;SDK archive&lt;/a&gt; and extract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/develop
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xf&lt;/span&gt; ~/Downloads/flutter_linux_3.41.0-stable.tar.xz &lt;span class="nt"&gt;-C&lt;/span&gt; ~/develop/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add Flutter to your PATH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="$HOME/develop/flutter/bin:$PATH"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Linux desktop development, additional packages are required as documented in the &lt;a href="https://docs.flutter.dev/platform-integration/linux/setup" rel="noopener noreferrer"&gt;Flutter Linux desktop setup guide&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never run Flutter commands with &lt;code&gt;sudo&lt;/code&gt; on Linux. It creates permission issues that are genuinely painful to unwind.&lt;/p&gt;




&lt;h2&gt;
  
  
  Android SDK setup
&lt;/h2&gt;

&lt;p&gt;Regardless of your operating system, the Android toolchain is where most &lt;code&gt;flutter doctor&lt;/code&gt; failures occur.&lt;/p&gt;

&lt;p&gt;Start by installing the latest Android Studio from the &lt;a href="https://developer.android.com/studio" rel="noopener noreferrer"&gt;Android Studio download page&lt;/a&gt;. During the setup wizard, Android Studio installs the base SDK, but the wizard does not install everything Flutter needs.&lt;/p&gt;

&lt;p&gt;Open the SDK Manager (&lt;strong&gt;Tools → SDK Manager&lt;/strong&gt;, or from the welcome screen: &lt;strong&gt;More Actions → SDK Manager&lt;/strong&gt;) and configure two tabs:&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;SDK Platforms&lt;/strong&gt; tab, install the platform for API Level 36 (Android 16).&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;SDK Tools&lt;/strong&gt; tab (this is the critical step that most guides gloss over), make sure all four of these are checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android SDK Command-line Tools (latest)&lt;/strong&gt;, the number one most common missing component&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android SDK Build-Tools&lt;/strong&gt; (latest)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android SDK Platform-Tools&lt;/strong&gt; (includes &lt;code&gt;adb&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Android Emulator&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Apply&lt;/strong&gt; and confirm. Without the command-line tools specifically, &lt;code&gt;flutter doctor&lt;/code&gt; will fail with: &lt;code&gt;cmdline-tools component is missing&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment variables for the Android SDK
&lt;/h3&gt;

&lt;p&gt;Set &lt;code&gt;ANDROID_HOME&lt;/code&gt; and add platform-tools to PATH. The default SDK locations differ by OS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS&lt;/strong&gt; (&lt;code&gt;~/.zshrc&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/Library/Android/sdk"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/platform-tools"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/cmdline-tools/latest/bin"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/emulator"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows&lt;/strong&gt; (System Environment Variables):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variable: &lt;code&gt;ANDROID_HOME&lt;/code&gt; = &lt;code&gt;C:\Users\{username}\AppData\Local\Android\Sdk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add to PATH: &lt;code&gt;%ANDROID_HOME%\platform-tools&lt;/code&gt; and &lt;code&gt;%ANDROID_HOME%\cmdline-tools\latest\bin&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Linux&lt;/strong&gt; (&lt;code&gt;~/.bashrc&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/Android/Sdk"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/platform-tools"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;&lt;span class="s2"&gt;/cmdline-tools/latest/bin"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After configuring the SDK, accept all Android licenses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter doctor &lt;span class="nt"&gt;--android-licenses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;y&lt;/code&gt; at each prompt. This step is mandatory before Flutter can build any Android app.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shorebird belongs in your initial setup, not as an afterthought
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://shorebird.dev/" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt;, founded by Flutter creator Eric Seidel, enables over-the-air code push, sending Dart code updates directly to users' devices without App Store or Play Store review cycles.&lt;/p&gt;

&lt;p&gt;Installing it alongside Flutter from day one avoids the common "retrofit under pressure" scenario where teams scramble to add OTA capability while rushing to fix a production bug. &lt;a href="https://docs.shorebird.dev/code-push/patch/" rel="noopener noreferrer"&gt;Patches&lt;/a&gt; are tied to exact release versions, so having Shorebird configured early prevents version-matching headaches down the road.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation commands
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh &lt;span class="nt"&gt;-sSf&lt;/span&gt; | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows (PowerShell):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RemoteSigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CurrentUser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;iwr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UseBasicParsing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'https://raw.githubusercontent.com/shorebirdtech/install/main/install.ps1'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shorebird installs to &lt;code&gt;~/.shorebird/bin&lt;/code&gt; and automatically adds itself to PATH. It also installs a private copy of Flutter inside &lt;code&gt;~/.shorebird/bin/cache/flutter&lt;/code&gt;. This is Shorebird's modified Flutter for code push and should not be added to your PATH. The total installation size is approximately 300 MB.&lt;/p&gt;

&lt;p&gt;Verify the installation by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shorebird &lt;span class="nt"&gt;--version&lt;/span&gt;
shorebird doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;shorebird doctor&lt;/code&gt; output checks connectivity to Shorebird's API, console, OAuth, storage, and CDN endpoints, and verifies that the Flutter installation is correct. A clean output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ https://api.shorebird.dev OK
✓ https://console.shorebird.dev OK
✓ Shorebird is up-to-date
✓ Flutter install is correct
No issues detected!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core workflow from there is straightforward: &lt;code&gt;shorebird login&lt;/code&gt; authenticates via Google OAuth, &lt;code&gt;shorebird init&lt;/code&gt; &lt;a href="https://docs.shorebird.dev/code-push/initialize/" rel="noopener noreferrer"&gt;initializes your project&lt;/a&gt; and creates &lt;code&gt;shorebird.yaml&lt;/code&gt; with your app ID, &lt;code&gt;shorebird release android&lt;/code&gt; or &lt;code&gt;shorebird release ios&lt;/code&gt; &lt;a href="https://docs.shorebird.dev/code-push/release/" rel="noopener noreferrer"&gt;creates a release&lt;/a&gt;, and &lt;code&gt;shorebird patch android/ios&lt;/code&gt; &lt;a href="https://docs.shorebird.dev/code-push/patch/" rel="noopener noreferrer"&gt;pushes OTA updates&lt;/a&gt;. The &lt;a href="https://shorebird.dev/blog/shorebird-create/" rel="noopener noreferrer"&gt;&lt;code&gt;shorebird create&lt;/code&gt; command&lt;/a&gt; also scaffolds new projects with OTA, CI/CD, and release tooling baked in from the start.&lt;/p&gt;

&lt;p&gt;Shorebird is purely CLI-driven and works seamlessly alongside the standard Flutter and Dart VS Code extensions.&lt;/p&gt;




&lt;h2&gt;
  
  
  VS Code configuration for Flutter development
&lt;/h2&gt;

&lt;p&gt;Install the &lt;strong&gt;Flutter extension&lt;/strong&gt; (ID: &lt;code&gt;Dart-Code.flutter&lt;/code&gt;) from the VS Code marketplace. It automatically installs the &lt;strong&gt;Dart extension&lt;/strong&gt; (&lt;code&gt;Dart-Code.dart-code&lt;/code&gt;) as a dependency. These two extensions provide debugging, hot reload, widget inspector, code completion, refactoring, and snippets.&lt;/p&gt;

&lt;p&gt;After installation, open the Command Palette and run &lt;code&gt;Dart: Use Recommended Settings&lt;/code&gt; to apply sensible defaults, or add these to your &lt;code&gt;settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"[dart]"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"editor.formatOnSave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"editor.formatOnType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"editor.rulers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"editor.selectionHighlight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"editor.wordBasedSuggestions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"editor.tabCompletion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"onlySnippets"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"editor.codeActionsOnSave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"source.fixAll"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"debug.internalConsoleOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openOnSessionStart"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few shortcuts worth committing to memory: &lt;code&gt;F5&lt;/code&gt; starts debugging, &lt;code&gt;Ctrl+F5&lt;/code&gt; / &lt;code&gt;Cmd+F5&lt;/code&gt; triggers hot reload, &lt;code&gt;Ctrl+Shift+F5&lt;/code&gt; performs a hot restart, and &lt;code&gt;Ctrl+.&lt;/code&gt; / &lt;code&gt;Cmd+.&lt;/code&gt; opens quick-fix actions for wrapping or extracting widgets. Creating a new project goes through the Command Palette: &lt;strong&gt;Flutter: New Project → select Application → choose directory → enter name&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reading &lt;code&gt;flutter doctor&lt;/code&gt; output and fixing common errors
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;flutter doctor -v&lt;/code&gt; (verbose mode) after completing setup. The command checks eight categories: Flutter SDK, Android toolchain, Xcode (macOS only), Chrome, Android Studio, VS Code, connected devices, and network resources. Each line is prefixed with one of three symbols:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symbol&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Action needed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;[✓]&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Passed&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;[!]&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Warning&lt;/td&gt;
&lt;td&gt;May need attention, development often still works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;[✗]&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;td&gt;Must fix before building for that platform&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A fully healthy macOS output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[✓] Flutter (Channel stable, 3.41.1, on macOS 26.2 25C56 darwin-arm64, locale
    en-IN) [605ms]
    • Flutter version 3.41.1 on channel stable at
      /Users/&amp;lt;username&amp;gt;/development/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 582a0e7c55 (5 days ago), 2026-02-12 17:12:32 -0800
    • Engine revision 3452d735bd
    • Dart version 3.11.0
    • DevTools version 2.54.1

[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0)
    [1,702ms]
    • Android SDK at /Users/&amp;lt;username&amp;gt;/Library/Android/sdk
    • Platform android-36-ext19, build-tools 36.1.0
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 26.2) [1,475ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web [7ms]
[✓] Connected device (3 available) [7.2s]
[✓] Network resources [6.8s]

• No issues found!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most frequently encountered errors and their fixes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;flutter: command not found&lt;/code&gt; means Flutter is not in PATH. Re-add the &lt;code&gt;export PATH=...&lt;/code&gt; line to the correct shell config file (&lt;code&gt;~/.zshrc&lt;/code&gt;, &lt;code&gt;~/.bashrc&lt;/code&gt;, or Windows Environment Variables) and restart your terminal.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cmdline-tools component is missing&lt;/code&gt; means you need to open Android Studio → Tools → SDK Manager → SDK Tools tab → check "Android SDK Command-line Tools (latest)" → Apply.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Android license status unknown&lt;/code&gt; is fixed by running &lt;code&gt;flutter doctor --android-licenses&lt;/code&gt; and accepting each license with &lt;code&gt;y&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Unable to find bundled Java version&lt;/code&gt; happens because newer Android Studio versions renamed the &lt;code&gt;jre&lt;/code&gt; directory to &lt;code&gt;jbr&lt;/code&gt;. On macOS, create a symlink: &lt;code&gt;cd "/Applications/Android Studio.app/Contents" &amp;amp;&amp;amp; ln -s jbr jre&lt;/code&gt;. This issue is resolved in recent Flutter and Android Studio combinations, but persists if either is outdated.&lt;/p&gt;

&lt;p&gt;CocoaPods failures on Apple Silicon typically mean you're using system Ruby instead of Homebrew Ruby. Run &lt;code&gt;which ruby&lt;/code&gt; and confirm it shows &lt;code&gt;/opt/homebrew/opt/ruby/bin/ruby&lt;/code&gt;, not &lt;code&gt;/usr/bin/ruby&lt;/code&gt;. Reinstall CocoaPods via &lt;code&gt;brew install cocoapods&lt;/code&gt; if needed.&lt;/p&gt;

&lt;p&gt;Lock file errors during &lt;code&gt;pub get&lt;/code&gt; are resolved by running &lt;code&gt;flutter clean&lt;/code&gt;, then &lt;code&gt;flutter pub cache repair&lt;/code&gt;, then &lt;code&gt;flutter pub get&lt;/code&gt;. If the problem persists, delete &lt;code&gt;pubspec.lock&lt;/code&gt; and retry.&lt;/p&gt;

&lt;p&gt;"No devices available" means either no emulator is running, no physical device is connected, or platform support isn't enabled. Start an emulator from Android Studio's Device Manager or run &lt;code&gt;flutter emulators --launch&lt;/code&gt; with an available emulator name.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The 2026 Flutter installation process has matured considerably. The VS Code quick-install path now handles SDK download and PATH configuration in one step, and Swift Package Manager offers a welcome alternative to the historically fragile CocoaPods dependency chain.&lt;/p&gt;

&lt;p&gt;Three setup decisions matter most for production teams: choosing Homebrew on macOS (it handles architecture differences automatically), installing Android SDK Command-line Tools explicitly via SDK Manager (the universal failure point that no automated wizard handles for you), and integrating Shorebird from project inception rather than retrofitting it under deadline pressure.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;flutter doctor -v&lt;/code&gt; and &lt;code&gt;shorebird doctor&lt;/code&gt; after every installation step. If both report no issues, your environment is production-ready.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>webdev</category>
      <category>programming</category>
      <category>mobile</category>
    </item>
    <item>
      <title>React Native vs Flutter for Enterprise Apps: Making the Right Choice in 2026</title>
      <dc:creator>Kumar Harsh</dc:creator>
      <pubDate>Mon, 23 Feb 2026 13:21:46 +0000</pubDate>
      <link>https://dev.to/kumarharsh/react-native-vs-flutter-for-enterprise-apps-making-the-right-choice-in-2026-40id</link>
      <guid>https://dev.to/kumarharsh/react-native-vs-flutter-for-enterprise-apps-making-the-right-choice-in-2026-40id</guid>
      <description>&lt;p&gt;In 2026, choosing between React Native and Flutter is no longer a developer preference debate. It has turned into a long-term platform decision.&lt;/p&gt;

&lt;p&gt;Both frameworks are mature. Both run serious production apps. Both promise shared code across iOS and Android.&lt;/p&gt;

&lt;p&gt;But once you look closer, the trade-offs start to matter. They differ in how they render, how stable their ecosystems feel, how painful upgrades can be, and how much control you keep over your release process.&lt;/p&gt;

&lt;p&gt;The real decision isn’t about raw speed. It’s about risk. How often will platform changes disrupt you? What does maintenance look like after a few years in? Can you ship urgent fixes instantly? How will this choice affect compliance, security, and long-term cost?&lt;/p&gt;

&lt;p&gt;This guide focuses on those practical differences so you can choose a foundation that still makes sense years from now.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Rendering Architecture Actually Works
&lt;/h2&gt;

&lt;p&gt;Enterprise apps serve millions of users across dozens of device models. When your banking app renders a transfer button with the wrong theme on Android but with the right one on iOS, users notice. When your retail checkout stutters during holiday traffic, revenue drops. Performance matters.&lt;/p&gt;

&lt;p&gt;Flutter owns the entire rendering stack. The &lt;a href="https://docs.flutter.dev/perf/impeller" rel="noopener noreferrer"&gt;Impeller engine&lt;/a&gt; (which replaced &lt;a href="https://skia.org/" rel="noopener noreferrer"&gt;Skia&lt;/a&gt; in 2024) compiles shaders ahead-of-time at build. No runtime compilation means no noticeable jank in animation. Impeller uses Metal on iOS and Vulkan on Android, talking directly to the GPU without OS-mediated layers.&lt;/p&gt;

&lt;p&gt;Production data backs this up. Flutter's 2026 &lt;a href="https://devnewsletter.com/p/state-of-flutter-2026/#:~:text=The%20impact%20proved,causes%20visible%20stutters." rel="noopener noreferrer"&gt;benchmarks show 30-50% fewer jank frames during complex animations compared to the old Skia renderer&lt;/a&gt;. The &lt;a href="https://www.synergyboat.com/blog/flutter-vs-react-native-vs-native-performance-benchmark-2025?utm_campaign=state-of-flutter-2026&amp;amp;utm_medium=referral&amp;amp;utm_source=devnewsletter.com#:~:text=Flutter%3A%20(Dropped%200%25%2C%20Jank%200%25)%C2%A0" rel="noopener noreferrer"&gt;SynergyBoat benchmark&lt;/a&gt; measured worst-case frame drops: 0% dropped frames with Flutter (using Impeller) versus 15.51% with React Native and 1.61% with Swift (native). For real-time trading apps or video streaming interfaces, that difference is user-visible.&lt;/p&gt;

&lt;p&gt;React Native took a different path with the &lt;a href="https://reactnative.dev/docs/the-new-architecture/landing-page" rel="noopener noreferrer"&gt;New Architecture&lt;/a&gt;. JSI (JavaScript Interface) replaced the old asynchronous bridge, enabling direct C++ communication. Fabric handles rendering with synchronous layout calculations. The performance gap closed significantly, but React Native still maps your components to native iOS and Android UI elements. You write &lt;code&gt;&amp;lt;Text&amp;gt;&lt;/code&gt;, it renders a &lt;code&gt;UILabel&lt;/code&gt; on iOS or &lt;code&gt;TextView&lt;/code&gt; on Android.&lt;/p&gt;

&lt;p&gt;This creates the core trade-off. React Native components look and feel native because they are native. Your app automatically inherits iOS's San Francisco font and Material Design typography on Android. Flutter draws everything itself, so you explicitly choose how text renders across platforms.&lt;/p&gt;

&lt;p&gt;But that native component dependency means OS updates can break your UI silently. When &lt;a href="https://appleinsider.com/articles/23/07/18/hands-on-with-all-the-new-messages-features-in-ios-17#:~:text=This%20update%20adds%20the%20ability,sticker%20from%20a%20home%20video" rel="noopener noreferrer"&gt;iOS 17 changed how &lt;code&gt;TextInput&lt;/code&gt; handled emoji&lt;/a&gt;, React Native apps inherited that behavior. Flutter apps rendered identically. When Samsung ships a One UI update that modifies &lt;code&gt;TextView&lt;/code&gt; padding, your React Native app on Galaxy devices changes behavior. Flutter stays consistent.&lt;/p&gt;

&lt;p&gt;The practical impact shows up in QA. Samsung phones in India, Xiaomi devices in China, and Google Pixels in the US all run different Android builds with vendor modifications. Flutter's pixel-level control means you test once. React Native's native component reliance means testing device matrices, because Samsung-specific quirks only appear on Galaxy phones. It all comes down to “are you in control of the user experience you’re offering, or is the platform?”&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deployment Problem
&lt;/h2&gt;

&lt;p&gt;Enterprise operations teams need to be in control. When a payment bug surfaces on Black Friday, or a healthcare app crashes for patients with specific conditions, you can't wait 48 hours for Apple's review team. This requirement is non-negotiable.&lt;/p&gt;

&lt;p&gt;React Native teams have had an advantage over other platforms in over-the-air updates for years. Microsoft's App Center CodePush was a reliable infrastructure. You pushed JavaScript updates, users got them instantly, and it integrated cleanly with existing CI/CD pipelines. Enterprises built critical workflows around it.&lt;/p&gt;

&lt;p&gt;Then Microsoft shut it down. The &lt;a href="https://learn.microsoft.com/en-us/appcenter/retirement" rel="noopener noreferrer"&gt;retirement announcement&lt;/a&gt; gave teams one year to migrate. That sounds generous until you're managing production apps with millions of users and complex deployment pipelines.&lt;/p&gt;

&lt;p&gt;The migration options were limited: self-host the open-source CodePush server (which Microsoft deliberately kept as a private "special version"), use Expo EAS, or build your own. Most chose Expo EAS. It works, but it's not a drop-in replacement.&lt;/p&gt;

&lt;p&gt;While &lt;a href="https://docs.expo.dev/eas-update/standalone-service" rel="noopener noreferrer"&gt;EAS Update works without requiring other EAS services&lt;/a&gt;, it is still designed with the entire ecosystem in mind. If you want to make full use of EAS’s bookkeeping and insights features, you would need to refactor the existing CI/CD built on GitHub Actions, Jenkins, or internal tools. In such a case, you're not just buying OTA updates, you're adopting Expo's managed workflow. For enterprises with established build infrastructure and security requirements about where code compiles, this creates friction.&lt;/p&gt;

&lt;p&gt;The pricing model compounds at scale. Expo charges per Monthly Active User (anyone who downloads at least one update) plus bandwidth. Every update downloads the full JavaScript bundle. A 12MB bundle to 500,000 users consumes around 6TB of bandwidth. Ship monthly hotfixes, that's 72TB annually.&lt;/p&gt;

&lt;p&gt;As of February 2026, Expo's enterprise tier starts around $1,000-2,000 monthly with usage-based charges on top. A realistic enterprise scenario: fintech app, 500,000 active users, monthly security patches. Base cost plus overages runs $25,000-30,000 annually just for OTA capability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://shorebird.dev/" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt; works differently. You keep your existing build system. Shorebird provides a modified Flutter engine that enables &lt;a href="https://docs.shorebird.dev/code-push/" rel="noopener noreferrer"&gt;code push&lt;/a&gt;, but you still run your builds wherever you want. Run &lt;code&gt;shorebird release&lt;/code&gt; instead of &lt;code&gt;flutter build&lt;/code&gt;, push artifacts to stores normally, then &lt;code&gt;shorebird patch&lt;/code&gt; for instant updates.&lt;/p&gt;

&lt;p&gt;The pricing is transparent: usage-based on &lt;a href="https://shorebird.dev/pricing/" rel="noopener noreferrer"&gt;patch installs&lt;/a&gt;. Free tier covers 5,000 installs monthly, then $0.01 per install. Same scenario (500,000 users, monthly patches): in the Business tier, the cost would come out to be $400 per month, or $4800 annually.&lt;/p&gt;

&lt;p&gt;For targeted rollouts (shipping fixes only to premium users or specific regions), the economics diverge further. Expo charges for all MAUs, whether they get updates or not. Shorebird charges only for actual installations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security and Supply Chain
&lt;/h2&gt;

&lt;p&gt;Enterprise security teams evaluate frameworks through attack surface, auditability, and supply chain trust. Both frameworks pass basic requirements (certificate pinning, OAuth integration, secure storage), but architectural differences create distinct risk profiles.&lt;/p&gt;

&lt;p&gt;A standard JavaScript/React project pulls anywhere between 700 and 1,500 packages from npm on install. Each package can execute code during installation via pre-install scripts. Each has its own dependencies. Any of those packages could be compromised.&lt;/p&gt;

&lt;p&gt;Flutter's &lt;a href="https://pub.dev/" rel="noopener noreferrer"&gt;pub.dev&lt;/a&gt; ecosystem is smaller (55,000 packages versus npm's 3+ million), more curated, and critically, pub doesn't execute code during package installation. The attack surface for supply chain compromises is structurally smaller. For enterprises implementing the &lt;a href="https://slsa.dev/" rel="noopener noreferrer"&gt;SLSA framework&lt;/a&gt; compliance, a smaller, controlled dependency tree is easier to audit and monitor.&lt;/p&gt;

&lt;p&gt;Code obfuscation provides another layer. Flutter compiles Dart to native ARM machine code using AOT compilation. The resulting binary is difficult to reverse-engineer, comparable to native Swift or Kotlin apps. React Native ships JavaScript bundles. Even minified and obfuscated, they remain more accessible. Tools like &lt;a href="https://github.com/skylot/jadx" rel="noopener noreferrer"&gt;jadx&lt;/a&gt; can decompile React Native bundles in minutes. Flutter's compiled binaries require significantly more effort.&lt;/p&gt;

&lt;p&gt;Shorebird's OTA architecture was designed around security constraints. &lt;a href="https://docs.shorebird.dev/code-push/guides/patch-signing/" rel="noopener noreferrer"&gt;Patch signing&lt;/a&gt; uses Ed25519 cryptographic signatures. Patches are signed with your private key, which never leaves your infrastructure. The Shorebird CDN delivers patches but can't modify them. For enterprises in regulated industries (fintech, healthcare, government), this separation of delivery from signing authority is essential.&lt;/p&gt;

&lt;p&gt;Expo EAS provides similar signing, but because EAS controls your build environment, you're trusting Expo's infrastructure for key security. Many enterprises prefer Shorebird's model, where keys stay in your control, integrated with existing secrets management (HashiCorp Vault, AWS KMS, Azure Key Vault).&lt;/p&gt;

&lt;h2&gt;
  
  
  Brownfield Integration
&lt;/h2&gt;

&lt;p&gt;Enterprise mobile strategy rarely starts from zero. Most large organizations already ship native iOS and Android apps generating revenue and serving millions of users. The question becomes: how do we modernize without rewriting everything?&lt;/p&gt;

&lt;p&gt;React Native was designed for this. Facebook built it to add features to their massive native app without total rewrites. The integration story is mature. Create a React Native view controller on iOS or a fragment on Android, initialize the JavaScript runtime, and render React Native screens alongside native UI. Companies like Shopify and Microsoft used this approach for years, gradually increasing React Native adoption screen by screen.&lt;/p&gt;

&lt;p&gt;Flutter's "Add-to-App" module matured significantly in Flutter 3.x. You embed Flutter screens in existing native apps with comparable integration effort to React Native. The Flutter engine initializes, renders to a native view, and communicates with native code through &lt;a href="https://docs.flutter.dev/platform-integration/platform-channels" rel="noopener noreferrer"&gt;platform channels&lt;/a&gt;. &lt;a href="https://innovation.ebayinc.com/stories/ebay-motors-accelerating-with-fluttertm/" rel="noopener noreferrer"&gt;eBay Motors adopted Flutter&lt;/a&gt; for adding specific features in existing native apps.&lt;/p&gt;

&lt;p&gt;The practical difference is ecosystem tooling. React Native benefits from nearly a decade of community tools for brownfield integration. Documentation covers edge cases, popular libraries handle common patterns, and StackOverflow has answers. Flutter's brownfield story works but has less community polish.&lt;/p&gt;

&lt;p&gt;For greenfield deployments (new apps or complete rewrites), Flutter is clearer. You're not managing bridge complexity between JavaScript and native code. You write Dart, compile to native, and avoid the architectural complexity React Native's JSI introduces. Virgin Money consolidated separate iOS and Android codebases into a unified Flutter, reporting faster feature delivery and reduced QA complexity.&lt;/p&gt;

&lt;p&gt;The hiring consideration factors in. JavaScript developers outnumber Dart specialists roughly 5:1 in most markets. Need to scale from 5 to 25 engineers in six months? You'll fill React Native roles faster. But quality distribution differs.&lt;/p&gt;

&lt;p&gt;Flutter developers tend to be mobile specialists who understand platform constraints: memory management, lifecycle handling, and push notifications. React Native's web-to-mobile pipeline often surfaces developers strong in React but weaker in mobile fundamentals.&lt;/p&gt;

&lt;p&gt;This manifests as different productivity curves. React Native teams onboard faster (web devs ship mobile code in weeks), but hit mobile-specific challenges where the web mental model breaks. Flutter teams take longer to onboard (learning Dart and Flutter's widget model), but once productive, ship more reliably because the framework forces mobile-first thinking.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision Framework
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Flutter + Shorebird&lt;/th&gt;
&lt;th&gt;React Native + Expo EAS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rendering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Impeller (Metal/Vulkan). Direct GPU access. Pixel-perfect consistency. Immune to OS UI changes.&lt;/td&gt;
&lt;td&gt;Native components via Fabric. Authentically native look. Subject to OEM behavior changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OTA Updates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shorebird. Patch signing. Any CI/CD. Installs-based pricing.&lt;/td&gt;
&lt;td&gt;Expo EAS. Full bundle downloads. Requires Expo builds. MAU + bandwidth based pricing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native AOT to ARM. Consistent 60/120fps. Predictable worst-case frame times.&lt;/td&gt;
&lt;td&gt;JSI removes bridge overhead. Hermes precompiles. Good for most UIs, complex animations may need optimization.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strong typing. Binary obfuscation by default. Small dependency graph (55K packages).&lt;/td&gt;
&lt;td&gt;Minified JavaScript is reversible. Large npm attack surface (3M+ packages). Needs additional hardening.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Supply Chain&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Curated pub.dev. Google-maintained core. No install-time code execution.&lt;/td&gt;
&lt;td&gt;npm ecosystem. Around 1000 packages standard. Preinstall scripts can execute code.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hiring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Smaller pool. Mobile-specialist focus. Dart requires learning.&lt;/td&gt;
&lt;td&gt;Massive JavaScript pool. Lower barrier from web. Variable mobile expertise.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Brownfield&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Add-to-App stable. Less mature tooling.&lt;/td&gt;
&lt;td&gt;Excellent. Designed for incremental adoption. Mature community tools.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Platforms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mobile, web, desktop, embedded. Single codebase.&lt;/td&gt;
&lt;td&gt;Mobile-focused. Web/desktop exist but less mature.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Total Cost Over 5 Years
&lt;/h2&gt;

&lt;p&gt;Enterprise mobile investment spans 5-10 years. The framework decision compounds over that timeline. React Native appears cheaper Day 1: faster hiring, larger talent pool, lower learning curve. But structural costs emerge in Years 2-5.&lt;/p&gt;

&lt;p&gt;JavaScript ecosystem volatility creates a maintenance burden. Major packages break compatibility. React Native ships breaking changes regularly. The New Architecture migration required significant refactoring for most teams. The 2024 State of React Native survey found 62% of teams spent significant effort upgrading dependencies, and 31% described dependency management as their biggest pain point.&lt;/p&gt;

&lt;p&gt;Flutter's &lt;a href="https://docs.flutter.dev/release/breaking-changes" rel="noopener noreferrer"&gt;structured releases&lt;/a&gt; and Google-maintained core packages provide more stability. The framework has breaking changes, but they're fewer and better documented. Enterprise teams report reduced maintenance burden compared to React Native codebases at 2-3 years.&lt;/p&gt;

&lt;p&gt;Platform updates hit differently. When iOS or Android ships a new version, Flutter apps generally work without changes. The rendering engine is independent of OS UI components. React Native apps often require updates to handle new OS behavior because they use native components that the OS updates.&lt;/p&gt;

&lt;p&gt;The deployment cost equation changed with App Center's retirement. React Native teams now pay Expo's MAU pricing or build their own OTA infrastructure. Shorebird's transparent usage pricing makes Flutter's deployment costs predictable and often lower at enterprise scale.&lt;/p&gt;

&lt;p&gt;Here's a real scenario:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Year 1&lt;/th&gt;
&lt;th&gt;Years 2-3&lt;/th&gt;
&lt;th&gt;Years 4-5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React Native&lt;/td&gt;
&lt;td&gt;Lower initial cost&lt;/td&gt;
&lt;td&gt;Dependency upgrades, platform update fixes&lt;/td&gt;
&lt;td&gt;Accumulated tech debt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;Higher learning cost&lt;/td&gt;
&lt;td&gt;Stable dependencies, OS-independent rendering&lt;/td&gt;
&lt;td&gt;Predictable maintenance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Fintech app, 500,000 active users, 5-year timeline. React Native Year 1 costs less (faster development, easier hiring). But Years 2-5 include Expo EAS OTA costs ($25,000-30,000 annually), dependency upgrade sprints (2-3 weeks annually), and platform update fixes (1-2 weeks per major OS release). Accumulated TCO over 5 years runs roughly $150,000-200,000 beyond base development.&lt;/p&gt;

&lt;p&gt;Flutter Year 1 costs more (Dart learning, slightly longer development). But Years 2-5 include Shorebird OTA costs (around $16,000-30,000 annually with better control), fewer upgrade sprints, and minimal platform update work. Accumulated TCO over 5 years runs roughly $130,000-170,000 beyond base development.&lt;/p&gt;

&lt;p&gt;The TCO model favors Flutter for long-lived, maintained codebases. Higher upfront cost pays back through lower maintenance burden, predictable upgrade paths, and simpler platform update handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Call
&lt;/h2&gt;

&lt;p&gt;Choose React Native if you're web-first with strong React expertise, building features that need an authentic iOS/Android native feel, on a short timeline where hiring speed matters more than long-term maintenance. The JavaScript ecosystem and lower learning curve make it pragmatic for shops already invested in web stacks.&lt;/p&gt;

&lt;p&gt;Choose Flutter if you're building an asset meant to last 5-10 years, where UI consistency across platforms matters more than per-platform authenticity, where security audits demand supply chain control, and where deployment agility can't sacrifice security for convenience. With Shorebird, you're not choosing between performance and agility. You get both.&lt;/p&gt;

&lt;p&gt;The App Center retirement was a forcing function. It exposed React Native's dependence on external services for critical enterprise features. Flutter's answer, through Shorebird, is &lt;a href="https://docs.shorebird.dev/code-push/system-architecture/" rel="noopener noreferrer"&gt;integrated deployment built into the framework&lt;/a&gt; without forcing managed build services or unpredictable pricing.&lt;/p&gt;

&lt;p&gt;For CTOs evaluating mobile strategy in 2026, the math changed. Flutter closed its deployment gap. The performance and consistency advantages remain. The security posture is structurally stronger. The TCO favors Flutter for long-lived projects.&lt;/p&gt;

&lt;p&gt;If you've worked with both frameworks at scale, I'd love to hear how your experience compares to what I've laid out here. What did I miss? Where does your data differ? Drop a comment or reach out directly.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>reactnative</category>
      <category>flutter</category>
      <category>programming</category>
    </item>
    <item>
      <title>Flutter vs React Native in 2026: A Technical Architecture Comparison</title>
      <dc:creator>Kumar Harsh</dc:creator>
      <pubDate>Thu, 19 Feb 2026 21:00:13 +0000</pubDate>
      <link>https://dev.to/kumarharsh/flutter-vs-react-native-in-2026-a-technical-architecture-comparison-13pd</link>
      <guid>https://dev.to/kumarharsh/flutter-vs-react-native-in-2026-a-technical-architecture-comparison-13pd</guid>
      <description>&lt;p&gt;If you're choosing a cross-platform framework in 2026, you're probably looking at Flutter and React Native. Both have matured significantly, and honestly, either can work for most projects. But the decision still matters because the architectural differences will affect your team's productivity and your app's performance.&lt;/p&gt;

&lt;p&gt;Here's what has changed: Flutter shipped Impeller, a rendering engine that addressed shader compilation jank on iOS, where Apple’s move to out-of-process shader compilation made Skia’s just-in-time shader model increasingly expensive during app startup. Native Swift apps were unaffected because their shaders were already compiled ahead of time. Flutter, despite using a relatively fixed shader set, still paid the runtime cost. Impeller moved shader compilation to build time, eliminating that class of startup stutter on iOS.&lt;/p&gt;

&lt;p&gt;React Native’s New Architecture tackled a different bottleneck. By replacing the legacy bridge with JSI’s synchronous C++ interface, it removed the serialization overhead that previously constrained performance and concurrency. &lt;/p&gt;

&lt;p&gt;The performance gap has narrowed. React Native now achieves 92-99% latency reduction in native module calls through JSI. Flutter still has a slight edge on consistent frame rates and startup time, but we're talking about differences that won't matter for most apps.&lt;/p&gt;

&lt;p&gt;The real decision comes down to this: Flutter gives you pixel-perfect consistency across platforms and about 20% lower maintenance costs over time. React Native gives you access to a JavaScript talent pool that's 20x larger and faster MVP development if you already have React (or web) developers.&lt;/p&gt;

&lt;p&gt;Let me walk you through the technical details so you can make an informed choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flutter's Impeller Fixes the Shader Jank Problem
&lt;/h2&gt;

&lt;p&gt;For years, Flutter had an annoying problem. The first time your app encountered a new animation or visual effect, it would stutter. This happened because Flutter's old renderer, Skia, compiled shaders at runtime. Each new draw command triggered shader compilation that took 100-300+ milliseconds, way over the 16ms budget you need for smooth 60fps rendering.&lt;/p&gt;

&lt;p&gt;Impeller solves this completely. Instead of compiling shaders when your app runs, &lt;a href="https://docs.flutter.dev/perf/impeller" rel="noopener noreferrer"&gt;Impeller compiles everything ahead of time&lt;/a&gt;. Shaders are written in GLSL 4.60, converted to SPIRV during the build, then transpiled to &lt;a href="https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf" rel="noopener noreferrer"&gt;Metal Shading Language&lt;/a&gt; for iOS or kept as SPIRV for &lt;a href="https://www.vulkan.org/" rel="noopener noreferrer"&gt;Vulkan&lt;/a&gt; on Android. All pipeline state objects get packaged into your app bundle, adding about 100KB per architecture.&lt;/p&gt;

&lt;p&gt;The result? Your app renders smoothly from frame one. No jank, no stuttering.&lt;/p&gt;

&lt;p&gt;Here's how the pipeline looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNpVUttu4jAQ_ZWR9zWwSbjk8lCJcGu6VEUN6kNDH9zaJNbGduQ4VVug377GAbH1g-XROWfOzHj26E0SimJUKFyXsEm2AsyZ5MtVtoJhf-xCVmJCVfMCvd4NJHnSsorAhnEKUyneDcKkeOlkieVM82ydPj5BKjRVnBKGNT0TppYw22-wKqiGdYX1Tip-7NDZCT2wh-w3x28P2QHm-T3VuLIlMFHACouixcUlW8efCKIkIwdY5E9t9RcLsPZnztw6LvO1or1XU7oxZTWtmKCQ6Wthi47WBUsb3OaTuoakFaSi8O257p8EpOm3pJicVbeWmObPVEl4bIXupsJrZhq7jiW1tLs841LqEhZMNRoWCvOLe3c3-tM4pbBjVRX_itz5PHL_R-5-IMgxf8YIirVqqYO4GTU-hWh_0myRLimnWxSb5ytuzGsrjkZTY_EsJb_IlGyLEsU7XDUmamtiRjJj2GzDlUKF-f-pNO2hOLIZULxHHyj2BoN-4IeR63l-4Aeegz5R7Pv9aBQG0dgLhm44GIdHB31ZR7cfjoZjfxSZ4wcj3w0dZLZDS3Xf7aBdxeM_cjvHiw" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNpVUttu4jAQ_ZWR9zWwSbjk8lCJcGu6VEUN6kNDH9zaJNbGduQ4VVug377GAbH1g-XROWfOzHj26E0SimJUKFyXsEm2AsyZ5MtVtoJhf-xCVmJCVfMCvd4NJHnSsorAhnEKUyneDcKkeOlkieVM82ydPj5BKjRVnBKGNT0TppYw22-wKqiGdYX1Tip-7NDZCT2wh-w3x28P2QHm-T3VuLIlMFHACouixcUlW8efCKIkIwdY5E9t9RcLsPZnztw6LvO1or1XU7oxZTWtmKCQ6Wthi47WBUsb3OaTuoakFaSi8O257p8EpOm3pJicVbeWmObPVEl4bIXupsJrZhq7jiW1tLs841LqEhZMNRoWCvOLe3c3-tM4pbBjVRX_itz5PHL_R-5-IMgxf8YIirVqqYO4GTU-hWh_0myRLimnWxSb5ytuzGsrjkZTY_EsJb_IlGyLEsU7XDUmamtiRjJj2GzDlUKF-f-pNO2hOLIZULxHHyj2BoN-4IeR63l-4Aeegz5R7Pv9aBQG0dgLhm44GIdHB31ZR7cfjoZjfxSZ4wcj3w0dZLZDS3Xf7aBdxeM_cjvHiw%3Ftype%3Dpng" width="464" height="1058"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pipeline follows a layered architecture where the Aiks layer translates high-level drawing commands into entities, which generate self-contained rendering instructions processed through a hardware abstraction layer. Impeller uses a stencil-then-cover tessellation strategy where complex vector paths are broken into triangles for GPU processing through adaptive subdivision algorithms. Production benchmarks show dramatic improvements: complex clipping operations dropped from 450ms to 11ms, Gaussian blur CPU+GPU costs nearly halved, and approximately 100MB less memory consumption while maintaining performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  React Native's JSI Enables Synchronous Native Communication
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://reactnative.dev/docs/the-new-architecture/landing-page" rel="noopener noreferrer"&gt;New Architecture&lt;/a&gt;, comprising JSI, Fabric, and TurboModules, represents React Native's most significant technical evolution since its 2015 release. &lt;a href="https://reactnative.dev/docs/the-new-architecture/pillars-turbomodules" rel="noopener noreferrer"&gt;JSI (JavaScript Interface)&lt;/a&gt; is a lightweight C++ API enabling direct synchronous communication between JavaScript and native code, eliminating the serialization overhead that defined React Native's original design.&lt;/p&gt;

&lt;p&gt;The old architecture was a mess. Every call between JavaScript and native code had to be asynchronous, passing JSON-serialized data through a message queue. You'd serialize on the sender side, insert into a queue, switch thread contexts, deserialize, then repeat the whole process for callbacks. It was slow and complicated.&lt;/p&gt;

&lt;p&gt;JSI replaces all of that with direct memory references. JavaScript can hold references to C++ host objects and call methods synchronously without serialization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://reactnative.dev/architecture/fabric-renderer" rel="noopener noreferrer"&gt;Fabric&lt;/a&gt;, the new concurrent rendering system, builds on JSI to create a shared C++ shadow tree synchronized with React's component model. The rendering pipeline operates in three phases: render (creating shadow nodes synchronously via JSI), commit (&lt;a href="https://www.yogalayout.dev/" rel="noopener noreferrer"&gt;Yoga layout&lt;/a&gt; calculation and tree promotion), and mount (diff computation and atomic mutations to native views). This enables React's concurrent features, Suspense, Transitions, automatic batching, which were architecturally impossible with the legacy bridge.&lt;/p&gt;

&lt;p&gt;Here's how the pipeline looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNp1ksFO4zAQhl9lNHsNVeLSJvEBiaYButoiBGil3YSDt5k2luK4chzYUvXdcd3sqhzwwfL4--e3x-M9rnRFyHFjxLaG51nZghvXxSOJlYVMq61uqbXwbIhe4OLiCmaOtRUZeKhFRy-nhJlHWZEZEpbgqRaVfoN7593BqxTw_WkxKDOvnBfOWkn7yWTuUV780hsBP8RO9-4Goln1jbBSt4Mq96qb4ngjeDBa6TN44-FtsdR9-9n81pO7Yi7Xa19Xb89d7zxeFNdWK7mC5UA7sBru3fKV4Kekt26Qn-bO7hpyFa1l0_BvSZzl-eyc5F-SxUDSMM_TEAP3_rJCbk1PASoyShxD3B9zSrQ1KSqRu-UfV1GJZXtwOVvR_tZa_Uszut_UyNei6VzUbyvXibkUrrPq_67xrcuOr4N8zLwH8j3-RR6Nx6OYJWkYRSxmcRTgDjljo3SSxOk0ii_DZDxNDgG--zPDUTK5nLJJ6gaLJyxMA6RKWm2Wpx_lP9bhA2z4uJ0" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp1ksFO4zAQhl9lNHsNVeLSJvEBiaYButoiBGil3YSDt5k2luK4chzYUvXdcd3sqhzwwfL4--e3x-M9rnRFyHFjxLaG51nZghvXxSOJlYVMq61uqbXwbIhe4OLiCmaOtRUZeKhFRy-nhJlHWZEZEpbgqRaVfoN7593BqxTw_WkxKDOvnBfOWkn7yWTuUV780hsBP8RO9-4Goln1jbBSt4Mq96qb4ngjeDBa6TN44-FtsdR9-9n81pO7Yi7Xa19Xb89d7zxeFNdWK7mC5UA7sBru3fKV4Kekt26Qn-bO7hpyFa1l0_BvSZzl-eyc5F-SxUDSMM_TEAP3_rJCbk1PASoyShxD3B9zSrQ1KSqRu-UfV1GJZXtwOVvR_tZa_Uszut_UyNei6VzUbyvXibkUrrPq_67xrcuOr4N8zLwH8j3-RR6Nx6OYJWkYRSxmcRTgDjljo3SSxOk0ii_DZDxNDgG--zPDUTK5nLJJ6gaLJyxMA6RKWm2Wpx_lP9bhA2z4uJ0" width="276" height="950"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://reactnative.dev/docs/turbo-native-modules-introduction" rel="noopener noreferrer"&gt;Turbo Native Modules&lt;/a&gt; use JSI for lazy module loading: instead of initializing all native modules at startup (the legacy pattern), modules load on first access. Combined with &lt;a href="https://reactnative.dev/docs/the-new-architecture/what-is-codegen" rel="noopener noreferrer"&gt;Codegen's&lt;/a&gt; compile-time type safety from TypeScript specs, this yields faster cold starts and eliminates an entire class of runtime type errors. The architecture became default in &lt;a href="https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here" rel="noopener noreferrer"&gt;React Native 0.76&lt;/a&gt; (October 2024), with the legacy architecture frozen and scheduled for removal.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Native Widget Mapping Creates Platform Fragility
&lt;/h2&gt;

&lt;p&gt;React Native maps your components to native widgets. When you write &lt;code&gt;&amp;lt;View&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Text&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;lt;Image&amp;gt;&lt;/code&gt;, React Native creates corresponding native components, &lt;code&gt;UIView&lt;/code&gt;/&lt;code&gt;ViewGroup&lt;/code&gt;, &lt;code&gt;UILabel&lt;/code&gt;/&lt;code&gt;TextView&lt;/code&gt;, &lt;code&gt;UIImageView&lt;/code&gt;/&lt;code&gt;ImageView&lt;/code&gt;. This gives you authentic platform behavior, but it also means you’re abstracting over components that are not truly equivalent. As views grow more complex, developers often end up writing JavaScript that either compensates for platform-specific differences, which undermines the promise of a single shared layer, or ignores them and produces interactions that feel subtly wrong on one or both platforms&lt;/p&gt;

&lt;p&gt;Flutter draws every pixel independently using its own &lt;a href="https://docs.flutter.dev/resources/architectural-overview#rendering-and-layout" rel="noopener noreferrer"&gt;rendering engine&lt;/a&gt;, never delegating to platform UI components for core widgets. A Flutter &lt;code&gt;Container&lt;/code&gt; or &lt;code&gt;Text&lt;/code&gt; widget renders identically on iOS 15 and iOS 18, Android 12 and Android 15. The only dependency is on lower-level graphics APIs (Metal, Vulkan, OpenGL ES) which have stable interfaces. This architectural difference explains Flutter's pixel-perfect cross-platform consistency versus React Native's platform-adaptive appearance that may diverge between iOS and Android.&lt;/p&gt;

&lt;p&gt;The trade-off shows up in maintenance patterns: React Native teams must monitor OS releases for widget behavior changes and third-party library compatibility, while Flutter teams primarily track framework updates on a predictable quarterly cycle. &lt;a href="https://shopify.engineering/migrating-our-largest-mobile-app-to-react-native" rel="noopener noreferrer"&gt;Shopify's 2025 migration documentation&lt;/a&gt; reveals concrete examples of this fragility, state batching changes exposed component issues, shadow tree manipulation caused tap gesture failures, and view flattening unexpectedly optimized out components with refs.&lt;/p&gt;

&lt;h2&gt;
  
  
  OTA Updates Work Fundamentally Differently Across Frameworks
&lt;/h2&gt;

&lt;p&gt;Over-the-air update mechanisms reflect the fundamental architectural difference between Dart's AOT compilation and JavaScript's interpreted execution. &lt;a href="https://shorebird.dev/" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt;, Flutter's primary OTA solution, had to engineer an entirely novel approach: a custom Dart interpreter that can execute patched code alongside the original AOT binary. Here's what it looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNpdUtuO2jAQ_ZWRq31qltrBIRdVW0EIUqXuRe32oQUeDJklVhM7chwtFNhvr0kAtfXDaC7nHB_ZsydrnSNJyMaIuoDnyUKBO2M2fzRyI5UoYfz4DBOXmd0Sbm_vYMLmadtYXcEXqX6hgbFD7RrZLM9cf_6Ar_Ak7LqA1KlfaP14wroyZftZq9ZWagVpIdQG80_HHpF2iMODhji6eX-AKZt_xbZBuFrq7XxcmQ93s7YsIX36Dt9qxHz5j8IPbODNvzlAdrU8FcbCZ2XR1AZd7DTeGKVbaEr9iuasMO1dzhxRVyupMIdsi-v25PcMyS6QvuxjY3clntgvsiyTdzHNspj-PZr9PyKee3uZk8SaFj1SoanEqST7E21BbIEVLkji0pVoXLZQR8ephfqpdXWhGd1uCpK8iLJxVVvnwuJUCver1bVrUOVoUt0qSxI_6DRIsidbkrDIHwyDgEecBrEfUjr0yM61Qz6IeBSxkHE-pGwUHD3yu7uWDkajMIwizoOYx47KPYK5tNrc9wvV7dXxDzm1ttE" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNpdUtuO2jAQ_ZWRq31qltrBIRdVW0EIUqXuRe32oQUeDJklVhM7chwtFNhvr0kAtfXDaC7nHB_ZsydrnSNJyMaIuoDnyUKBO2M2fzRyI5UoYfz4DBOXmd0Sbm_vYMLmadtYXcEXqX6hgbFD7RrZLM9cf_6Ar_Ak7LqA1KlfaP14wroyZftZq9ZWagVpIdQG80_HHpF2iMODhji6eX-AKZt_xbZBuFrq7XxcmQ93s7YsIX36Dt9qxHz5j8IPbODNvzlAdrU8FcbCZ2XR1AZd7DTeGKVbaEr9iuasMO1dzhxRVyupMIdsi-v25PcMyS6QvuxjY3clntgvsiyTdzHNspj-PZr9PyKee3uZk8SaFj1SoanEqST7E21BbIEVLkji0pVoXLZQR8ephfqpdXWhGd1uCpK8iLJxVVvnwuJUCver1bVrUOVoUt0qSxI_6DRIsidbkrDIHwyDgEecBrEfUjr0yM61Qz6IeBSxkHE-pGwUHD3yu7uWDkajMIwizoOYx47KPYK5tNrc9wvV7dXxDzm1ttE%3Ftype%3Dpng" width="510" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.shorebird.dev/code-push/system-architecture/" rel="noopener noreferrer"&gt;Shorebird's technical implementation&lt;/a&gt; leverages Dart's origins as a JIT language. Even though Flutter uses AOT mode, the language architecture maintains source code information enabling different compiled representations of the same function. When developers &lt;a href="https://docs.shorebird.dev/code-push/patch/" rel="noopener noreferrer"&gt;publish a patch&lt;/a&gt;, Shorebird's modified compiler generates code "maximally similar" to the previous version, with a custom linker analyzing both programs to determine which functions can reuse the original binary. The result: 98%+ of code (typically including the Flutter framework itself) runs at full native CPU speed, while only changed code executes through the interpreter at approximately 100x slower speed. For typical patches affecting application logic rather than framework code, &lt;a href="https://docs.shorebird.dev/code-push/performance/" rel="noopener noreferrer"&gt;overall performance remains unchanged&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the case of React Native, Microsoft CodePush was retired on March 31, 2025, making &lt;a href="https://docs.expo.dev/versions/latest/sdk/updates/" rel="noopener noreferrer"&gt;Expo Updates&lt;/a&gt; (EAS Update) the primary maintained OTA solution for React Native. JavaScript's interpreted nature enables simpler bundle replacement: the runtime simply loads a different JS bundle on next launch. Expo's architecture separates apps into a native layer (built into the binary) and an update layer (swappable JavaScript bundles and assets). This is how it looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNpNkU1vwjAMhv9K5B1XWEtTGqoJCeg4TPvSttPaHUJraKQ0QWk62BD_fWlhEzn5tfO8TuwDFLpESGBj-LYi72muiDuzMHs2YiMUl-T-jcxbVUr8JIPBlMzD7Al35IXboiKzLy4kX7naiZuH_Z1FmKV6p6TmJVm28sLjdmVuptekc5g1DdrmDC5OYBpmr7iVvEByp6wweIlp5bi9JQ-8VUV1BnPV2G-JncFaSJlcLZdp7Pu5As_9SZSQWNOiBzWamncSDh2Yg62wxhwSF65446JcHR2z5epD6_oPM7rdVJCsuWycarclt5gK7qZV_2cNqhLNQrfKQhIw1ptAcoB9J0fDMIooo340GbmHhR58u3RMh4wyFsQBpaEfjKOjBz99X384HscxY5RGEzpxKPUAS2G1eTxtql_Y8ReJloYp" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNpNkU1vwjAMhv9K5B1XWEtTGqoJCeg4TPvSttPaHUJraKQ0QWk62BD_fWlhEzn5tfO8TuwDFLpESGBj-LYi72muiDuzMHs2YiMUl-T-jcxbVUr8JIPBlMzD7Al35IXboiKzLy4kX7naiZuH_Z1FmKV6p6TmJVm28sLjdmVuptekc5g1DdrmDC5OYBpmr7iVvEByp6wweIlp5bi9JQ-8VUV1BnPV2G-JncFaSJlcLZdp7Pu5As_9SZSQWNOiBzWamncSDh2Yg62wxhwSF65446JcHR2z5epD6_oPM7rdVJCsuWycarclt5gK7qZV_2cNqhLNQrfKQhIw1ptAcoB9J0fDMIooo340GbmHhR58u3RMh4wyFsQBpaEfjKOjBz99X384HscxY5RGEzpxKPUAS2G1eTxtql_Y8ReJloYp" width="255" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Updates publish to branches linked to channels, with runtime version strings ensuring JS-native interface compatibility. However, unlike Shorebird's differential approach, Expo’s bundle diffing is currently in beta and &lt;a href="https://docs.expo.dev/eas-update/bundle-diffing/#current-limitations" rel="noopener noreferrer"&gt;has a few limitations&lt;/a&gt;. In its regular operation, Expo downloads full JS bundles plus new assets, potentially megabytes for complex applications, though smart asset caching mitigates this somewhat.&lt;/p&gt;

&lt;p&gt;Both platforms share critical limitations: neither can update native code, framework versions, or native dependencies OTA. Both comply with &lt;a href="https://developer.apple.com/app-store/review/guidelines/#software-requirements" rel="noopener noreferrer"&gt;Apple's App Store guidelines&lt;/a&gt; (section 2.5.2) permitting interpreted code downloads that don't change an app's primary purpose or bypass security features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Benchmarks Reveal Nuanced Trade-Offs
&lt;/h2&gt;

&lt;p&gt;The 2024-2025 generation of cross-platform frameworks has largely eliminated the "which is faster?" debate through architectural maturity. Both Flutter and React Native now achieve native-class performance for most use cases, but &lt;a href="https://www.synergyboat.com/blog/flutter-vs-react-native-vs-native-performance-benchmark-2025?" rel="noopener noreferrer"&gt;according to a detailed 2025 benchmark comparison&lt;/a&gt; across identical test apps on real devices, all tested stacks (Flutter, React Native, and native) complete their first frame in under ~50 ms, with Flutter consistently showing the fastest first-frame latency.&lt;/p&gt;

&lt;p&gt;Flutter demonstrates strong frame pacing and smoothness across refresh rates. In steady and dynamic rendering tests, it maintains smooth visuals with more spare scheduling headroom at both 60 Hz and 120 Hz than React Native. React Native's rendering remains solid at steady state, though it can require platform-specific tuning on iOS to minimize dropped frames. Native Android tracking closely behind Flutter in rendering consistency underscores that vector.&lt;/p&gt;

&lt;p&gt;Under memory profiling, Flutter maintains a more stable memory footprint over time, while React Native exhibits more noticeable growth during prolonged or UI-heavy sessions. Although neither framework approaches problematic limits in these tests, the difference highlights Flutter's tendency toward steadier resource usage in long-running views.&lt;/p&gt;

&lt;p&gt;Flutter's rendering model maintains consistent frame delivery as UI workloads grow, with fewer timing fluctuations during list scrolling and animated transitions. React Native performs well in typical interaction patterns, but benchmark traces show greater variability when layout recalculation and JavaScript execution compete for time, especially on iOS without platform-specific tuning.&lt;/p&gt;

&lt;p&gt;These results align with architectural differences rather than raw speed claims. In practice, both frameworks perform well, but Flutter offers slightly more performance headroom, whereas React Native benefits from rapid iteration and mature tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hiring Dynamics Favor React Native Despite Flutter's Momentum
&lt;/h2&gt;

&lt;p&gt;Both Flutter and React Native continue to expand, but for different reasons.&lt;/p&gt;

&lt;p&gt;React Native’s growth tracks the web ecosystem. React remains one of the most widely adopted frontend frameworks globally, and React Native benefits directly from that gravity. Organizations that standardize on React for web products often extend that investment into mobile, reinforcing demand for React Native roles and sustaining its hiring advantage.&lt;/p&gt;

&lt;p&gt;As a result, teams with strong web backgrounds can often reach productivity in 1-2 months using React Native. Flutter typically requires onboarding into Dart and a different architectural model, which can stretch ramp-up to 2-3 months for teams without prior exposure.&lt;/p&gt;

&lt;p&gt;However, Flutter shows stronger momentum among developers exploring new technologies. GitHub users favor &lt;a href="https://github.com/flutter/flutter" rel="noopener noreferrer"&gt;Flutter (170,000 stars)&lt;/a&gt; to &lt;a href="https://github.com/facebook/react-native" rel="noopener noreferrer"&gt;React Native (121,000 stars)&lt;/a&gt;, &lt;a href="https://survey.stackoverflow.co/2024/" rel="noopener noreferrer"&gt;Stack Overflow surveys&lt;/a&gt; show Flutter at 9.4% versus React Native's 8.4% among professional developers, and &lt;a href="https://www.statista.com/statistics/869224/worldwide-software-developer-working-hours/" rel="noopener noreferrer"&gt;Statista's cross-platform framework surveys&lt;/a&gt; show Flutter with 46% market share versus React Native's 35%. &lt;/p&gt;

&lt;p&gt;Salary comparisons are more nuanced. Compensation varies significantly by region, and React Native roles often overlap with broader JavaScript and web development positions, which can influence reported averages. In markets where mobile-specialized roles are clearly defined, Flutter developers tend to earn slightly more (about 7% more) on average, though the gap is modest rather than dramatic.&lt;/p&gt;

&lt;p&gt;The trend lines suggest coexistence rather than replacement. React Native continues to expand through web-first organizations. Flutter grows among teams that prioritize rendering control, multi-platform reach, or architectural independence from the web stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem Completeness Differs in Philosophy
&lt;/h2&gt;

&lt;p&gt;Flutter provides a complete SDK with extensive built-in functionality: &lt;a href="https://docs.flutter.dev/ui/design/material" rel="noopener noreferrer"&gt;Material 3&lt;/a&gt; and &lt;a href="https://docs.flutter.dev/ui/widgets/cupertino" rel="noopener noreferrer"&gt;Cupertino widget libraries&lt;/a&gt;, integrated &lt;a href="https://docs.flutter.dev/tools/devtools" rel="noopener noreferrer"&gt;DevTools&lt;/a&gt;, and first-party Google packages for &lt;a href="https://pub.dev/packages/camera" rel="noopener noreferrer"&gt;camera&lt;/a&gt;, &lt;a href="https://pub.dev/packages/google_maps_flutter" rel="noopener noreferrer"&gt;maps&lt;/a&gt;, &lt;a href="https://pub.dev/packages/webview_flutter" rel="noopener noreferrer"&gt;webview&lt;/a&gt;, and &lt;a href="https://pub.dev/packages/shared_preferences" rel="noopener noreferrer"&gt;storage&lt;/a&gt;. &lt;a href="https://firebase.google.com/docs/flutter/setup?platform=ios#available-plugins" rel="noopener noreferrer"&gt;Firebase plugins&lt;/a&gt; offer official Firebase integration. The philosophy of controlling every pixel means less reliance on native UI components and more consistent cross-platform behavior.&lt;/p&gt;

&lt;p&gt;React Native increasingly relies on &lt;a href="https://expo.dev/" rel="noopener noreferrer"&gt;Expo&lt;/a&gt;, which React Native's own documentation now recommends as the default framework. Expo provides &lt;a href="https://docs.expo.dev/router/introduction/" rel="noopener noreferrer"&gt;file-based routing&lt;/a&gt;, 50+ maintained native modules, and &lt;a href="https://expo.dev/eas" rel="noopener noreferrer"&gt;EAS (Expo Application Services)&lt;/a&gt; for cloud builds, submissions, and OTA updates. The trade-off: Expo SDKs trail React Native releases by weeks, and EAS pricing scales with usage.&lt;/p&gt;

&lt;p&gt;The Flutter partner ecosystem has matured significantly. &lt;a href="https://codemagic.io/" rel="noopener noreferrer"&gt;Codemagic&lt;/a&gt; (CI/CD launched at Flutter Live 2018) provides zero-config Flutter builds with Apple Silicon machines and automatic code signing. &lt;a href="https://shorebird.dev/" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt; extends Flutter’s deployment model with production-ready over-the-air updates, enabling teams to ship bug fixes without app store review. &lt;a href="https://serverpod.dev/" rel="noopener noreferrer"&gt;Serverpod&lt;/a&gt; ("the missing server for Flutter") enables full-stack Dart development with type-safe ORM and automatic code generation. &lt;a href="https://verygood.ventures/" rel="noopener noreferrer"&gt;Very Good Ventures&lt;/a&gt; contributes extensively through &lt;a href="https://github.com/VeryGoodOpenSource/very_good_cli" rel="noopener noreferrer"&gt;very_good_cli&lt;/a&gt;, providing production-ready scaffolding with BLoC architecture, 100% test coverage setup, and strict lint rules.&lt;/p&gt;

&lt;p&gt;React Native's partner ecosystem includes substantial contributions from &lt;a href="https://microsoft.github.io/react-native-windows/" rel="noopener noreferrer"&gt;Microsoft&lt;/a&gt; (react-native-windows/macos), Amazon (Vega OS using RN at the OS level), &lt;a href="https://shopify.engineering/" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt; (&lt;a href="https://shopify.github.io/react-native-skia/" rel="noopener noreferrer"&gt;react-native-skia&lt;/a&gt;, &lt;a href="https://shopify.github.io/flash-list/" rel="noopener noreferrer"&gt;flash-list&lt;/a&gt;), and &lt;a href="https://swmansion.com/" rel="noopener noreferrer"&gt;Software Mansion&lt;/a&gt; (&lt;a href="https://docs.swmansion.com/react-native-reanimated/" rel="noopener noreferrer"&gt;Reanimated&lt;/a&gt;, &lt;a href="https://docs.swmansion.com/react-native-gesture-handler/" rel="noopener noreferrer"&gt;Gesture Handler&lt;/a&gt;). The critical difference: Flutter's ecosystem is more cohesive around fewer, more integrated tools, while React Native's leverages the vast &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm ecosystem&lt;/a&gt; with all its quality variance.&lt;/p&gt;

&lt;h2&gt;
  
  
  TCO Analysis Shows Flutter Advantages for Long-Term Projects
&lt;/h2&gt;

&lt;p&gt;Total cost of ownership analysis suggests Flutter offers lower long-term maintenance despite higher upfront investment. &lt;a href="https://medium.com/@thehubops/flutter-vs-react-native-in-2025-what-dev-agencies-are-actually-using-ffc35e865e32" rel="noopener noreferrer"&gt;Forrester Research (2024)&lt;/a&gt; found Flutter applications required approximately 20% less maintenance time for equivalent functionality over two years. Case studies report 20-33% annual savings on maintenance costs after switching from React Native to Flutter.&lt;/p&gt;

&lt;p&gt;React Native's higher maintenance burden stems from bridge-related issues (improving with New Architecture), platform-specific fixes, and third-party library dependency. The "18-month rule" observation suggests skipping quarterly updates beyond 18 months transforms linear fixes into exponential problems. &lt;a href="https://shopify.engineering/migrating-our-largest-mobile-app-to-react-native" rel="noopener noreferrer"&gt;Shopify's migration documentation&lt;/a&gt; reveals real-world challenges: state batching exposed component issues, TurboModule implementation caused blank screens, and shadow tree manipulation created severe UI problems.&lt;/p&gt;

&lt;p&gt;Cross-platform code sharing is comparable: Flutter achieves 85-95% while React Native typically reaches 80-90% (&lt;a href="https://discord.com/blog/how-discord-achieves-native-ios-performance-with-react-native" rel="noopener noreferrer"&gt;Discord achieved 98%&lt;/a&gt; as an exceptional case). Both offer 25-35% savings versus native development for typical applications.&lt;/p&gt;

&lt;p&gt;The decisive factors map to organizational context. Flutter suits teams prioritizing long-term maintenance cost reduction, pixel-perfect brand consistency, multi-platform targets (mobile + web + desktop), and who can invest 2-3 months in Dart training. React Native suits teams with existing JavaScript/React expertise, tight MVP deadlines, need for the vast npm ecosystem, or budget constraints requiring the larger developer pool.&lt;/p&gt;

&lt;p&gt;A distinct case emerges when the target surface extends beyond iOS and Android phones. Teams building for kiosks, embedded displays, automotive dashboards, in-store devices, or desktop environments often prioritize consistent rendering and full control over the UI layer. Flutter’s self-contained rendering engine makes these expansions more predictable. React Native can target additional platforms, but success depends more heavily on the maturity of target-specific native implementations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Choice: A Decision Framework
&lt;/h2&gt;

&lt;p&gt;To make things simple for you, here's a decision tree to help you choose between Flutter and React Native based on your requirements:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNqFU1Fv2jAQ_isnT9oTtKGEEqKqUxtCu67t0EqnacCDIQd4S-zIdloY8N93ceiaaprmB8vnu--7u-90WzZXCbKQLTXPVzDqTyTQuRhHWhnTHKbcLpTOYKB5hs9K_4Q-zoURSk6h2TyHy228FsYKuYQR8gzidY7aCoMf9hXTZRm2e7BaUcgNf-IPcy1ye_wF-dzuIBq7B9xzK54QDnEDYad1-L168Qw1LlCjnOMO-tuhVj-Q0CORYSrkn6TV3XfYu69DOIM2ZEraldlB_CZjeDbTx-cDbixqWAldNvIelJwprhMypnWqW6qgSYEZ5FolRVn_YPsJN1SVUFpYgeZtBQMHG4o1pk3SZVHW-vjRpbwrUiua-UHfHVyNB2lhyyr-0qBiueV6iSDzDHCuzIYKzhzRq6ZglSIVlju4_o-qB0b1TOkyLqRFyUlSIGLrSCMlDY0VpQUSO0HtaK_qvUVu_h_HjwbLqSugPmCGxkL_2yFNXIVUxnXdqO4r93XjKB5WSuNM6MTxfB5dQJEn3KKZ1hHGblIk3EKkafiu58Vxz6t7ooMn6EZxfFn3xP_0XL_xsAatgkhYaHWBDZbRtHlpsm2JmTC7wgwnLKTnjBt6TeSeMDmX35XKXmBaFcsVCxc8NWRVnfQFpyV7DXG6RqqQloU9x8DCLVuzsBWcHLU7HT_wvU7vpOt57Qbb0HfXPwr8IGh1W77f9lqnnX2D_XJJvaPT0243CHy_0_N7BPUbDBNhlb6rttst-f43XzY-cA" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNqFU1Fv2jAQ_isnT9oTtKGEEqKqUxtCu67t0EqnacCDIQd4S-zIdloY8N93ceiaaprmB8vnu--7u-90WzZXCbKQLTXPVzDqTyTQuRhHWhnTHKbcLpTOYKB5hs9K_4Q-zoURSk6h2TyHy228FsYKuYQR8gzidY7aCoMf9hXTZRm2e7BaUcgNf-IPcy1ye_wF-dzuIBq7B9xzK54QDnEDYad1-L168Qw1LlCjnOMO-tuhVj-Q0CORYSrkn6TV3XfYu69DOIM2ZEraldlB_CZjeDbTx-cDbixqWAldNvIelJwprhMypnWqW6qgSYEZ5FolRVn_YPsJN1SVUFpYgeZtBQMHG4o1pk3SZVHW-vjRpbwrUiua-UHfHVyNB2lhyyr-0qBiueV6iSDzDHCuzIYKzhzRq6ZglSIVlju4_o-qB0b1TOkyLqRFyUlSIGLrSCMlDY0VpQUSO0HtaK_qvUVu_h_HjwbLqSugPmCGxkL_2yFNXIVUxnXdqO4r93XjKB5WSuNM6MTxfB5dQJEn3KKZ1hHGblIk3EKkafiu58Vxz6t7ooMn6EZxfFn3xP_0XL_xsAatgkhYaHWBDZbRtHlpsm2JmTC7wgwnLKTnjBt6TeSeMDmX35XKXmBaFcsVCxc8NWRVnfQFpyV7DXG6RqqQloU9x8DCLVuzsBWcHLU7HT_wvU7vpOt57Qbb0HfXPwr8IGh1W77f9lqnnX2D_XJJvaPT0243CHy_0_N7BPUbDBNhlb6rttst-f43XzY-cA%3Ftype%3Dpng" width="1127" height="1163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The 2026 cross-platform landscape no longer features one clearly superior framework. Both Flutter and React Native have addressed their historic weaknesses through substantial architectural investments. Flutter's Impeller renderer eliminates shader jank entirely while React Native's JSI removes the bridge bottleneck that defined its original limitations.&lt;/p&gt;

&lt;p&gt;The decision now hinges on strategic alignment rather than capability gaps. Flutter offers a more self-contained, consistent experience with demonstrably lower maintenance overhead and superior raw performance, but requires investment in &lt;a href="https://dart.dev/guides" rel="noopener noreferrer"&gt;Dart expertise&lt;/a&gt; and a smaller hiring pool. React Native leverages the world's most common programming language and mature ecosystem, but carries higher technical debt risk from npm dependencies and requires more architectural discipline at scale.&lt;/p&gt;

&lt;p&gt;For new projects without existing team expertise, Flutter's trajectory and developer satisfaction metrics increasingly favor it as the default choice. For organizations with React/JavaScript investment, React Native with Expo remains productive and now performs "good enough" for most applications. Both frameworks have earned their place in enterprise production. The choice is one of strategic fit rather than technical inadequacy.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>flutter</category>
      <category>reactnative</category>
      <category>programming</category>
    </item>
    <item>
      <title>Flutter for Beginners: Your First App in 30 Minutes</title>
      <dc:creator>Kumar Harsh</dc:creator>
      <pubDate>Wed, 04 Feb 2026 17:29:12 +0000</pubDate>
      <link>https://dev.to/kumarharsh/flutter-for-beginners-your-first-app-in-30-minutes-2kk8</link>
      <guid>https://dev.to/kumarharsh/flutter-for-beginners-your-first-app-in-30-minutes-2kk8</guid>
      <description>&lt;p&gt;I've been watching the Flutter ecosystem for a while now, and one thing that's always bugged me is the deployment workflow. You build an app, ship it to the app stores, and then sit around waiting for approval every time you need to push a fix. Sometimes that takes days or even weeks.&lt;/p&gt;

&lt;p&gt;Shorebird solves this problem by letting you push over-the-air &lt;a href="https://docs.shorebird.dev/code-push/" rel="noopener noreferrer"&gt;code push updates&lt;/a&gt; to your Flutter apps. The really cool part is their &lt;code&gt;shorebird create&lt;/code&gt; command, which scaffolds Flutter projects that support instant updates right from the start.&lt;/p&gt;

&lt;p&gt;In this tutorial, I'll walk you through building your first Flutter app with Shorebird. We'll cover everything from installation to your first release, and I'll explain the Flutter fundamentals along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Shorebird Installed
&lt;/h2&gt;

&lt;p&gt;Before you can create a project, you need the Shorebird command-line interface. The installation process is pretty straightforward regardless of your operating system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On macOS or Linux&lt;/strong&gt;, open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh &lt;span class="nt"&gt;-sSf&lt;/span&gt; | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;On Windows&lt;/strong&gt;, fire up PowerShell and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RemoteSigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CurrentUser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;iwr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UseBasicParsing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'https://raw.githubusercontent.com/shorebirdtech/install/main/install.ps1'&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands download Shorebird to &lt;code&gt;~/.shorebird/bin&lt;/code&gt;, including a modified &lt;a href="https://github.com/flutter/engine" rel="noopener noreferrer"&gt;Flutter engine&lt;/a&gt; that enables the code push stuff. This modified Flutter lives inside Shorebird's cache and won't mess with your existing Flutter installation. You'll keep using your normal &lt;a href="https://docs.flutter.dev/get-started/install" rel="noopener noreferrer"&gt;Flutter SDK&lt;/a&gt; for development.&lt;/p&gt;

&lt;p&gt;After installation, verify everything works by running &lt;code&gt;shorebird --version&lt;/code&gt;. Then authenticate with &lt;code&gt;shorebird login&lt;/code&gt;, which opens your browser so you can create a free Shorebird account or sign into an existing one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Your First OTA-Enabled Flutter App
&lt;/h2&gt;

&lt;p&gt;Here's where &lt;a href="https://docs.shorebird.dev/code-push/create/" rel="noopener noreferrer"&gt;&lt;code&gt;shorebird create&lt;/code&gt;&lt;/a&gt; really shines. Instead of running &lt;code&gt;flutter create&lt;/code&gt; and then manually configuring Shorebird later, this single command handles everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shorebird create my_flutter_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, &lt;code&gt;shorebird create&lt;/code&gt; does two things automatically. First, it runs &lt;code&gt;flutter create&lt;/code&gt; to scaffold a standard Flutter project with the familiar counter app template. Second, it runs &lt;a href="https://docs.shorebird.dev/code-push/initialize/" rel="noopener noreferrer"&gt;&lt;code&gt;shorebird init&lt;/code&gt;&lt;/a&gt; to configure your project for OTA updates and register it with Shorebird's cloud infrastructure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You might get prompted to log in using &lt;code&gt;shorebird login&lt;/code&gt; if this is your first time running the Shorebird CLI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The command generates a unique &lt;code&gt;app_id&lt;/code&gt; (something like &lt;code&gt;8c846e87-1461-4b09-8708-170d78331aca&lt;/code&gt;) that identifies your app in Shorebird's system. This ID determines which patches get delivered to which apps. Think of it as your app's fingerprint. The &lt;code&gt;app_id&lt;/code&gt; isn't secret, so you should commit it to version control.&lt;/p&gt;

&lt;p&gt;Beyond the standard Flutter files, &lt;code&gt;shorebird create&lt;/code&gt; adds or modifies a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;shorebird.yaml&lt;/strong&gt;: A new config file in your project root with your &lt;code&gt;app_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pubspec.yaml&lt;/strong&gt;: Updated to include &lt;code&gt;shorebird.yaml&lt;/code&gt; in the assets section&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AndroidManifest.xml&lt;/strong&gt;: Updated to include INTERNET permission (required for downloading patches)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding Your Flutter Project Structure
&lt;/h2&gt;

&lt;p&gt;Whether you use &lt;code&gt;shorebird create&lt;/code&gt; or &lt;code&gt;flutter create&lt;/code&gt;, the resulting project follows Flutter's standard directory layout. I think it's worth understanding this structure because you'll be working with these files constantly.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;lib/&lt;/strong&gt; folder contains all your &lt;a href="https://dart.dev/" rel="noopener noreferrer"&gt;Dart&lt;/a&gt; code. The entry point is &lt;strong&gt;lib/main.dart&lt;/strong&gt;, which has the &lt;code&gt;main()&lt;/code&gt; function that calls &lt;code&gt;runApp()&lt;/code&gt; with your root widget. As your app grows, you'll organize screens, widgets, and business logic into subdirectories within &lt;strong&gt;lib/&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;android/&lt;/strong&gt; folder holds Android-specific configuration, including Gradle build files and &lt;strong&gt;AndroidManifest.xml&lt;/strong&gt;. Unless you're integrating native Android code or configuring platform-specific settings, you probably won't need to edit files here directly.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;ios/&lt;/strong&gt; folder does the same thing for Apple platforms. It contains the Xcode workspace and iOS project files. Platform-specific configuration like &lt;code&gt;Info.plist&lt;/code&gt; lives here, and you'll need to visit this directory when setting up iOS-specific capabilities or signing certificates.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;pubspec.yaml&lt;/strong&gt; file at the project root is probably the most important configuration file. It defines your app's name, version, dependencies, and assets. When Shorebird creates or initializes a project, it adds &lt;strong&gt;shorebird.yaml&lt;/strong&gt; to the assets list here so the config file gets bundled with your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flutter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;uses-material-design&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;assets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;shorebird.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How Flutter's Widget Tree Works
&lt;/h2&gt;

&lt;p&gt;Flutter builds user interfaces through a hierarchical tree of widgets. Everything is a widget. You compose simple widgets into complex UIs by nesting them as children of other widgets.&lt;/p&gt;

&lt;p&gt;A typical app structure uses four fundamental widgets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scaffold&lt;/strong&gt; provides the &lt;a href="https://m3.material.io/" rel="noopener noreferrer"&gt;Material Design&lt;/a&gt; layout structure. It's basically a container for your app's major visual elements with properties for the app bar, body content, floating action buttons, drawers, and bottom navigation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AppBar&lt;/strong&gt; creates the top navigation bar. It typically displays a title, optional leading widget (like a menu icon), and trailing action buttons (like search or settings icons).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Center&lt;/strong&gt; is a layout widget that positions its single child in the middle of the available space. It's commonly used to center content within the Scaffold's body.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text&lt;/strong&gt; displays styled text on screen. It's one of the simplest widgets, but you'll use it all the time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how these widgets nest together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'My First App'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Hello, world!'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hierarchy, &lt;code&gt;Scaffold&lt;/code&gt; containing &lt;code&gt;AppBar&lt;/code&gt; and &lt;code&gt;Center&lt;/code&gt;, with &lt;code&gt;Text&lt;/code&gt; nested inside &lt;code&gt;Center&lt;/code&gt;, shows how Flutter builds UIs through composition rather than inheritance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stateless Widgets vs. Stateful Widgets
&lt;/h3&gt;

&lt;p&gt;Flutter has two kinds of widgets: ones that never change and ones that can update dynamically. Understanding this distinction is crucial.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;StatelessWidget&lt;/code&gt; represents UI that doesn't change based on user interaction. These widgets receive their configuration from parent widgets, store values in &lt;code&gt;final&lt;/code&gt; variables, and render the same output given the same inputs. Use &lt;code&gt;StatelessWidget&lt;/code&gt; for static content like labels, icons, or logos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Greeting&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Greeting&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Welcome to Flutter!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;StatefulWidget&lt;/code&gt; manages mutable state. When something needs to change, a counter incrementing, a form field updating, or data loading from an API, you need a StatefulWidget. It creates a companion &lt;code&gt;State&lt;/code&gt; object that persists across rebuilds and holds the mutable data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_CounterState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_CounterState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;_increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;_count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;_increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Count: &lt;/span&gt;&lt;span class="si"&gt;$_count&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key mechanism is &lt;code&gt;setState()&lt;/code&gt;. Calling it tells Flutter that the state has changed and triggers a rebuild of the widget, updating the UI to reflect the new values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hot Reload Changes Everything
&lt;/h3&gt;

&lt;p&gt;One of Flutter's best features is hot reload, which injects updated code into the running &lt;a href="https://dart.dev/tools/dart-vm" rel="noopener noreferrer"&gt;Dart VM&lt;/a&gt; without restarting your app. When you save a file, Flutter recompiles only the changed libraries, sends them to the device, and rebuilds the widget tree. This usually takes less than a second.&lt;/p&gt;

&lt;p&gt;The magic of hot reload is that it preserves your app's state. Your app stays on the same screen with the same data loaded while UI changes appear instantly. You don't need to re-navigate to a deeply nested screen or re-enter form data after every code change.&lt;/p&gt;

&lt;p&gt;To trigger hot reload, just save your file in VS Code or Android Studio (the IDEs auto-reload by default), or press &lt;code&gt;r&lt;/code&gt; in the terminal if you're running via &lt;code&gt;flutter run&lt;/code&gt;. For changes that affect initialization logic, like modifications to &lt;code&gt;main()&lt;/code&gt; or &lt;code&gt;initState()&lt;/code&gt;, use hot restart (press &lt;code&gt;R&lt;/code&gt;). This restarts the app from scratch but is still way faster than a full rebuild.&lt;/p&gt;

&lt;p&gt;Hot reload only works in debug mode. Release builds compile Dart to native code and don't support this feature, which is exactly why Shorebird's code push capability is so valuable for production apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Your First Change
&lt;/h2&gt;

&lt;p&gt;At this point, you have a fully working Flutter app with OTA updates wired in. Let's run it and see what we've got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what the app looks like on an Android device:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4mi2rywx49oena944x3q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4mi2rywx49oena944x3q.jpg" alt="Initial app UI on Android" width="360" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before shipping anything, let's make a small but visible change so you can see how Flutter responds to code edits. We'll tweak the app's theme color and update some text.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;lib/main.dart&lt;/strong&gt; in your editor. Near the top of the file, you'll find the &lt;code&gt;MaterialApp&lt;/code&gt; widget. This is where global application settings live, including theming.&lt;/p&gt;

&lt;p&gt;Look for the &lt;code&gt;theme&lt;/code&gt; property. By default, it looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;colorScheme:&lt;/span&gt; &lt;span class="n"&gt;ColorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;seedColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deepPurple&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the seed color to something different, like &lt;code&gt;Colors.blue&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;colorScheme:&lt;/span&gt; &lt;span class="n"&gt;ColorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;seedColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single change updates the color used by Material components like the app bar, buttons, and highlights across your entire app. Flutter's theming system works top-down, so modifying the theme here affects every widget below it in the tree.&lt;/p&gt;

&lt;p&gt;Next, scroll down to the widget that renders text on the screen. In the default counter app, you'll see a &lt;code&gt;Text&lt;/code&gt; widget inside the &lt;code&gt;body&lt;/code&gt; of a &lt;code&gt;Scaffold&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'You have pushed the button this many times:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace it with something custom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'Welcome to my first OTA-enabled Flutter app!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file and run the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within seconds, you should see the updated theme color and new text: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.postimg.cc%2FRZYtGPzv%2FWhats-App-Image-2026-01-21-at-14-29-22-%281%29.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.postimg.cc%2FRZYtGPzv%2FWhats-App-Image-2026-01-21-at-14-29-22-%281%29.jpg" alt="Updated app UI on Android" width="360" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This fast feedback loop is one of Flutter's biggest strengths. You edit Dart code, the framework rebuilds the widget tree, and the changes appear immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping Your App With Shorebird
&lt;/h2&gt;

&lt;p&gt;Once your app is ready for users, Shorebird provides commands to build, preview, and distribute releases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating a release&lt;/strong&gt; captures a snapshot of your compiled code that Shorebird stores in the cloud. This becomes the baseline for future patches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shorebird release android    &lt;span class="c"&gt;# Creates an Android release (.aab)&lt;/span&gt;
shorebird release ios        &lt;span class="c"&gt;# Creates an iOS release (.ipa)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://docs.shorebird.dev/code-push/release/" rel="noopener noreferrer"&gt;&lt;code&gt;shorebird release&lt;/code&gt;&lt;/a&gt; command builds your app using Shorebird's modified Flutter engine, uploads the compiled Dart code to Shorebird's servers, and outputs the artifacts you'll submit to the app stores. For Android, you get an &lt;code&gt;.aab&lt;/code&gt; file for the &lt;a href="https://docs.shorebird.dev/code-push/guides/stores/play-store/" rel="noopener noreferrer"&gt;Play Store&lt;/a&gt;. For iOS, an &lt;code&gt;.ipa&lt;/code&gt; for &lt;a href="https://docs.shorebird.dev/code-push/guides/stores/app-store/" rel="noopener noreferrer"&gt;App Store Connect&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previewing a release&lt;/strong&gt; lets you test the exact build that will ship to users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shorebird preview
➜  my_flutter_app shorebird preview
✓ Fetching releases &lt;span class="o"&gt;(&lt;/span&gt;0.5s&lt;span class="o"&gt;)&lt;/span&gt;
✓ Fetching releases &lt;span class="o"&gt;(&lt;/span&gt;0.5s&lt;span class="o"&gt;)&lt;/span&gt;
Which release would you like to preview? 1.0.0+1
✓ Fetching aab artifact &lt;span class="o"&gt;(&lt;/span&gt;0.4s&lt;span class="o"&gt;)&lt;/span&gt;
✓ Using stable track &lt;span class="o"&gt;(&lt;/span&gt;0.7s&lt;span class="o"&gt;)&lt;/span&gt;
✓ Extracting metadata &lt;span class="o"&gt;(&lt;/span&gt;1.0s&lt;span class="o"&gt;)&lt;/span&gt;
✓ Built apks: /Users/&amp;lt;username&amp;gt;/.shorebird/bin/cache/previews/dbad83ad-92fd-4228-af9a-014800f6efd7/android_1.0.0+1_1966896.apks &lt;span class="o"&gt;(&lt;/span&gt;2.9s&lt;span class="o"&gt;)&lt;/span&gt;
✓ Installing apks &lt;span class="o"&gt;(&lt;/span&gt;6.0s&lt;span class="o"&gt;)&lt;/span&gt;
✓ Starting app &lt;span class="o"&gt;(&lt;/span&gt;1.8s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://docs.shorebird.dev/code-push/preview/" rel="noopener noreferrer"&gt;&lt;code&gt;shorebird preview&lt;/code&gt;&lt;/a&gt; command downloads the release artifacts from Shorebird's cloud and installs them on a connected device or emulator. I find this particularly useful for verifying releases built on &lt;a href="https://docs.shorebird.dev/code-push/ci/github/" rel="noopener noreferrer"&gt;CI/CD servers&lt;/a&gt; before distributing them to end users.&lt;/p&gt;

&lt;p&gt;After your initial release is live in the stores, you can push instant updates with &lt;a href="https://docs.shorebird.dev/code-push/patch/" rel="noopener noreferrer"&gt;&lt;code&gt;shorebird patch&lt;/code&gt;&lt;/a&gt; whenever you fix bugs or add features. No app store review required.&lt;/p&gt;

&lt;p&gt;For instance, update the theme of the app to use the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;theme: ThemeData(
  colorScheme: .fromSeed(seedColor: Colors.deepPurple),
),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run &lt;code&gt;shorebird patch android&lt;/code&gt;. Once the command completes running, try closing and restarting an installed release of the app. The theme of the app should get updated on a fresh run:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpf9l25svppx78qcxzxei.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpf9l25svppx78qcxzxei.gif" alt="Releasing a patch" width="480" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, you will be able to view the patch on your Shorebird Console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvpy0g495uo0yitott8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvpy0g495uo0yitott8q.png" alt="Viewing the patches on Shorebird console" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If needed, you can rollback your patches easily from here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98tpkbutwuoc498h41pb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98tpkbutwuoc498h41pb.png" alt="Rolling back a patch" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Your Flutter Story?
&lt;/h2&gt;

&lt;p&gt;Starting a Flutter project with &lt;code&gt;shorebird create&lt;/code&gt; instead of &lt;code&gt;flutter create&lt;/code&gt; doesn't cost you anything in terms of development workflow. You still get the same project structure, the same hot reload experience, and the same widget-based UI development.&lt;/p&gt;

&lt;p&gt;What you gain is the ability to push critical fixes to users in minutes instead of waiting days for app store approval. For any Flutter project that will eventually ship to real users, building in code push capability from day one just makes sense.&lt;/p&gt;

&lt;p&gt;I'm curious to hear about your experience with Flutter and Shorebird. What features are you most excited to build? What challenges are you running into? Drop a comment below.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>flutter</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What is Flutter? A Complete Introduction for Developers</title>
      <dc:creator>Kumar Harsh</dc:creator>
      <pubDate>Wed, 21 Jan 2026 16:07:18 +0000</pubDate>
      <link>https://dev.to/kumarharsh/what-is-flutter-a-complete-introduction-for-developers-2108</link>
      <guid>https://dev.to/kumarharsh/what-is-flutter-a-complete-introduction-for-developers-2108</guid>
      <description>&lt;p&gt;I spent years watching development teams struggle with the same problem. They'd build their iOS app in Swift, then turn around and build it again in Kotlin for Android. Same features, same bugs to fix twice, and constant headaches keeping the two versions in sync.&lt;/p&gt;

&lt;p&gt;The alternative wasn't much better. You could wrap a web view and call it a day, but users could tell. The interface felt wrong, animations were janky, and you were essentially asking people to use a mobile website pretending to be an app.&lt;/p&gt;

&lt;p&gt;Flutter changed this calculation completely. It's Google's open-source UI toolkit that lets you build native applications for mobile, web, and desktop from a single codebase. But here's what makes it different from earlier attempts at cross-platform development: Flutter doesn't wrap native components. It draws its own pixels, giving you complete control over every frame that renders on screen.&lt;/p&gt;

&lt;p&gt;This isn't some experimental technology anymore. Companies like &lt;a href="https://flutter.dev/showcase/ebay" rel="noopener noreferrer"&gt;eBay Motors&lt;/a&gt; use it for their vehicle marketplace app. &lt;a href="https://flutter.dev/showcase/bmw" rel="noopener noreferrer"&gt;BMW&lt;/a&gt; runs their connected car experiences on it. &lt;a href="https://flutter.dev/showcase/nubank" rel="noopener noreferrer"&gt;Nubank&lt;/a&gt;, one of the world's largest digital banks, built their entire mobile platform with Flutter and serves over 100 million customers.&lt;/p&gt;

&lt;p&gt;I'm going to walk you through how Flutter actually works, why businesses and developers choose it over alternatives, and the ecosystem of tools that make it ready for production. If you're evaluating mobile frameworks or trying to understand why Flutter matters, this is what you need to know.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Flutter Works: The Architecture
&lt;/h2&gt;

&lt;p&gt;Most cross-platform frameworks act as translators. They take your code and convert it into native iOS and Android UI components at runtime. This creates performance problems because iOS buttons don't behave exactly like Android buttons, and that translation layer adds overhead.&lt;/p&gt;

&lt;p&gt;Flutter takes a completely different approach. It doesn't use native platform widgets at all. Instead, it draws every pixel itself using a high-performance graphics engine. Think of it like a video game engine, but for building apps.&lt;/p&gt;

&lt;p&gt;This starts with &lt;a href="https://skia.org/" rel="noopener noreferrer"&gt;Skia&lt;/a&gt;, the same 2D graphics library that powers Chrome and Android. In 2022-24, Flutter introduced and set as default &lt;a href="https://docs.flutter.dev/perf/impeller" rel="noopener noreferrer"&gt;Impeller&lt;/a&gt;, a next-generation rendering engine that compiles shaders ahead of time for better performance. When your Flutter app renders a button, it's not asking the OS for a button component. It's drawing the exact pixels that make up that button, down to the shadow and animation curves.&lt;/p&gt;

&lt;p&gt;Google chose &lt;a href="https://dart.dev/" rel="noopener noreferrer"&gt;Dart&lt;/a&gt; as the programming language for specific technical reasons. Dart compiles to native ARM machine code on mobile, which means your production app runs at full speed without a JavaScript bridge or interpreter. During development, Dart's Just-in-Time (JIT) compiler powers Hot Reload, one of Flutter's standout features. You can change your code and see the result in under a second, without losing your app's state or restarting.&lt;/p&gt;

&lt;p&gt;Everything in Flutter is built with widgets. Structural elements like rows and columns, styling properties like padding and margins, all widgets. You compose these together to build your interface. A button isn't a single object. It's a composition of widgets for gesture detection, padding, material effects, and text styling. This composability makes Flutter interfaces predictable and testable.&lt;/p&gt;

&lt;p&gt;The framework uses Ahead-of-Time (AOT) compilation for production builds. Your Dart code compiles directly to native ARM code, eliminating the runtime overhead of interpretation. This is why Flutter apps can maintain 60fps, or 120fps on high-refresh displays, consistently. The framework's &lt;a href="https://docs.flutter.dev/tools/devtools/performance#:~:text=This%20means%20that%2C%20approximately%20every%2016ms%2C%20the%20UI%20updates%20to%20reflect%20animations%20or%20other%20changes%20to%20the%20UI." rel="noopener noreferrer"&gt;rendering pipeline is designed to complete all work for a frame in under 16 milliseconds&lt;/a&gt;, the budget for 60fps rendering.&lt;/p&gt;

&lt;p&gt;Here's the other advantage: consistency across platforms. Because Flutter controls its own rendering, a button looks identical on iOS 15, Android 13, and a three-year-old device. You're not at the mercy of platform differences or OS version fragmentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developers and Businesses Choose Flutter
&lt;/h2&gt;

&lt;p&gt;Hot Reload changed how mobile developers work. Before Flutter, changing a UI element meant recompiling your app, reinstalling it, and navigating back to the screen you were working on. This cycle took 30-60 seconds, and you did it dozens of times per day.&lt;/p&gt;

&lt;p&gt;With Hot Reload, you save your file and see the change in a few seconds, with your app's current state preserved. You can iterate on animations, test edge cases, and experiment with designs at a pace that wasn't possible before. I've watched developers completely change how they approach UI work because of this feature alone.&lt;/p&gt;

&lt;p&gt;For businesses, the math is straightforward. You write one codebase instead of two. That means one team instead of separate iOS and Android teams. You don't need to synchronize features between platforms or maintain parity between codebases. When you fix a bug, it's fixed everywhere. When you ship a feature, it ships to all your users at once.&lt;/p&gt;

&lt;p&gt;Flutter gives you pixel-perfect control over your interface on every screen size. You're not limited by platform conventions or native component libraries. Companies building design systems value this control because they can match brand guidelines exactly, animate transitions the way designers envisioned, and create custom UI components that work identically across mobile, desktop, and web.&lt;/p&gt;

&lt;p&gt;Dart's null safety system, introduced in Dart 2.12, prevents null reference errors at compile time. These errors are responsible for billions of dollars in software failures. Dart's type system catches null errors before you ship code to users. The language also supports modern features like async/await for handling asynchronous operations, strong type inference, and extension methods for adding functionality to existing classes.&lt;/p&gt;

&lt;p&gt;The business case extends beyond engineering efficiency. Teams shipping Flutter apps report faster time-to-market because they're not coordinating two platform-specific releases. They can experiment more freely because changes are cheaper to make. They can maintain quality standards across all platforms without fragmenting their engineering resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flutter Ecosystem: Tools and Agencies
&lt;/h2&gt;

&lt;p&gt;A framework is only useful if you can actually ship production apps with it. Flutter has matured into a commercial ecosystem with specialized tools and agencies that handle the full application lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tooling Stack
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://shorebird.dev" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt; solves one of mobile development's biggest frustrations: app store review times. When you find a critical bug, you can't just push a fix. You have to submit an update, wait 24-48 hours for Apple's review (longer on weekends), then wait for users to actually install the update.&lt;/p&gt;

&lt;p&gt;Shorebird enables &lt;a href="https://docs.shorebird.dev/code-push/" rel="noopener noreferrer"&gt;over-the-air updates&lt;/a&gt; for Flutter apps. You can &lt;a href="https://docs.shorebird.dev/code-push/patch/" rel="noopener noreferrer"&gt;patch bugs&lt;/a&gt; and ship changes directly to your users' devices without going through the store review process. It works by updating your Dart code while keeping your native shell unchanged, which keeps you compliant with &lt;a href="https://docs.shorebird.dev/code-push/guides/stores/app-store/" rel="noopener noreferrer"&gt;App Store&lt;/a&gt; and &lt;a href="https://docs.shorebird.dev/code-push/guides/stores/play-store/" rel="noopener noreferrer"&gt;Play Store&lt;/a&gt; guidelines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codemagic.io/" rel="noopener noreferrer"&gt;Codemagic&lt;/a&gt; is a CI/CD platform built specifically for mobile apps. Generic CI systems like GitHub Actions work, but they weren't designed for the specific needs of mobile builds: managing certificates, provisioning profiles, platform-specific signing, and coordinating releases across multiple stores. Codemagic handles these mobile-specific workflows out of the box. It &lt;a href="https://docs.shorebird.dev/code-push/ci/codemagic/" rel="noopener noreferrer"&gt;integrates with Shorebird&lt;/a&gt; and provides specialized workflows for releasing to the App Store, Play Store, and Firebase App Distribution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://serverpod.dev/" rel="noopener noreferrer"&gt;ServerPod&lt;/a&gt; lets you build your backend in Dart. Most Flutter apps talk to a backend written in Node.js, Python, or Go. ServerPod lets you write full-stack applications in a single language. It includes an ORM, API generation, authentication, and real-time communication. If you're building a Flutter app and need a backend, ServerPod means your entire team can work in Dart with shared types and business logic between client and server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.widgetbook.io/" rel="noopener noreferrer"&gt;Widgetbook&lt;/a&gt; is Storybook for Flutter. It creates a catalog of your UI components where you can develop widgets in isolation, test them with different data, and document their usage. When you're building a design system or working on a team where designers need to review components, Widgetbook provides a development environment focused purely on UI components, separate from your app's business logic and navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Experts: Agencies
&lt;/h3&gt;

&lt;p&gt;Not every company has the internal expertise or bandwidth to build a Flutter app from scratch. For businesses looking to scale quickly without an internal team, hiring a specialized Flutter app development company is a common path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://verygood.ventures/" rel="noopener noreferrer"&gt;Very Good Ventures (VGV)&lt;/a&gt; is known for defining architecture patterns and best practices in the Flutter community. They've worked with companies like Betterment and Hamilton, building production Flutter apps that handle millions of users. VGV open-sources many of their tools, including &lt;a href="https://cli.vgv.dev/" rel="noopener noreferrer"&gt;Very Good CLI&lt;/a&gt; for scaffolding projects and Very Good Coverage for enforcing code coverage standards. When companies need to establish patterns for large Flutter codebases, VGV's architectural guidance shapes how teams structure their apps.&lt;/p&gt;

&lt;p&gt;LeanCode specializes in enterprise Flutter applications. They've built apps for European banks, healthcare providers, and logistics companies where security, compliance, and scale matter. Their expertise includes integrating Flutter with existing enterprise systems, implementing complex authentication flows, and optimizing performance for apps with heavy data requirements.&lt;/p&gt;

&lt;p&gt;A dedicated Flutter mobile app development company can accelerate time-to-market by using pre-built modules, established patterns, and deep knowledge of the platform's edge cases. These agencies have encountered and solved the problems you'll face, from handling offline sync to optimizing app size to debugging platform-specific issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flutter vs. The Competition
&lt;/h2&gt;

&lt;p&gt;You need to understand what you're trading off when you choose Flutter over alternatives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flutter vs. React Native
&lt;/h3&gt;

&lt;p&gt;React Native uses a JavaScript bridge to communicate with native platform components. When you render a button in React Native, your JavaScript code sends a message across this bridge to create a native iOS or Android button. This bridge adds latency and complexity.&lt;/p&gt;

&lt;p&gt;Flutter eliminates the bridge entirely by rendering its own components. This architectural difference means Flutter delivers more consistent performance, especially in complex UIs with heavy animation or frequent state changes.&lt;/p&gt;

&lt;p&gt;React Native's approach has an advantage: it uses actual native components, so updates to iOS or Android automatically apply to React Native apps. But this same characteristic creates consistency problems. Your React Native app can look and behave differently depending on the OS version. Flutter trades this automatic updating for guaranteed consistency across platforms and OS versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flutter vs. Native Development
&lt;/h3&gt;

&lt;p&gt;Building native apps in Swift and Kotlin gives you immediate access to every OS API and the latest platform features on day one. Flutter catches up quickly, usually within weeks of a platform update, but native development is always first to new capabilities.&lt;/p&gt;

&lt;p&gt;The tradeoff is maintenance burden. Native development means maintaining separate codebases with duplicated business logic, separate testing, and coordinated releases. For teams that can't afford dedicated iOS and Android engineers, or for companies where mobile isn't the core product, this duplication is expensive. Flutter significantly reduces this maintenance cost, though you give up the cutting edge of platform features.&lt;/p&gt;

&lt;p&gt;The decision often comes down to your constraints. If you need the absolute latest iOS features and your app's value is tied to platform-specific capabilities, native development makes sense. If you need to ship across platforms quickly, control your interface precisely, and minimize long-term maintenance, Flutter is the better choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Shorebird Fits in the Stack
&lt;/h2&gt;

&lt;p&gt;The app stores serve a purpose. They enforce security standards, review apps for policy compliance, and give users confidence about the software they install. But the review process creates a bottleneck. You can't respond to production issues quickly. You can't iterate based on user feedback without waiting days for approval.&lt;/p&gt;

&lt;p&gt;This is the "last mile" problem. Flutter solves the problem of writing once and deploying everywhere. But you still hit the bottleneck of app store review times when you need to update your app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.shorebird.dev/code-push/system-architecture/" rel="noopener noreferrer"&gt;Shorebird patches this last mile&lt;/a&gt;. It enables over-the-air updates that bypass the store review process. When you find a bug, you &lt;a href="https://docs.shorebird.dev/code-push/patch/" rel="noopener noreferrer"&gt;create a patch&lt;/a&gt; and push it directly to your users. They receive the update automatically, without reinstalling your app or waiting for store approval.&lt;/p&gt;

&lt;p&gt;Shorebird works by separating your Dart code from your native app shell. The shell, which contains platform-specific code, goes through normal store reviews. Your Dart code, which is most of your app, can be updated over-the-air.&lt;/p&gt;

&lt;p&gt;Shorebird integrates cleanly with the rest of the Flutter ecosystem. You can add Shorebird commands to your &lt;a href="https://docs.shorebird.dev/code-push/ci/codemagic/" rel="noopener noreferrer"&gt;Codemagic workflows&lt;/a&gt; to automatically create patches when you merge to production. Your developers continue using their existing tools. Shorebird adds one command to your &lt;a href="https://docs.shorebird.dev/code-push/guides/development-workflow/" rel="noopener noreferrer"&gt;deployment process&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The philosophy behind Shorebird is that big tech companies like Google, Meta, and Microsoft have solved these problems internally for their own apps. They have systems to update apps without store reviews. Shorebird makes these capabilities available to every Flutter developer, not just teams with the resources to build their own infrastructure.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>flutter</category>
      <category>dart</category>
    </item>
    <item>
      <title>A Deep Look at the Flutter SDK: What's Actually Under the Hood</title>
      <dc:creator>Kumar Harsh</dc:creator>
      <pubDate>Wed, 14 Jan 2026 10:58:49 +0000</pubDate>
      <link>https://dev.to/kumarharsh/a-deep-look-at-the-flutter-sdk-whats-actually-under-the-hood-1phl</link>
      <guid>https://dev.to/kumarharsh/a-deep-look-at-the-flutter-sdk-whats-actually-under-the-hood-1phl</guid>
      <description>&lt;p&gt;I've noticed something interesting when people talk about Flutter. Most of the time, they're really just talking about the widget framework, those Material and Cupertino components everyone loves. But what you're actually using is the &lt;a href="https://github.com/flutter/flutter" rel="noopener noreferrer"&gt;Flutter SDK&lt;/a&gt;, a much bigger system that includes a rendering engine, the UI framework, and a complete set of tools for building and shipping apps.&lt;/p&gt;

&lt;p&gt;This distinction matters more than you might think. The engine is what converts your Dart code into native machine code and actual pixels on screen. The framework gives you all those widgets and the layout system. The tooling wraps everything into something you can actually build, test, and ship to iOS, Android, web, and desktop.&lt;/p&gt;

&lt;p&gt;Here's where things get tricky. Once you finish your build, you're stuck with static binaries that have to go through app store reviews. If you've ever waited days for Apple to approve a critical bug fix, you know exactly what I'm talking about. It doesn't match how modern teams want to work.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk you through what's inside the Flutter SDK, how all these pieces connect, and how &lt;a href="https://shorebird.dev/" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt; extends it with a way to actually update your apps in production without waiting for app store approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Dart's Two-Way Compilation Actually Makes Sense
&lt;/h2&gt;

&lt;p&gt;Dart does something pretty clever. It supports both Just-In-Time (JIT) and Ahead-Of-Time (AOT) compilation, and each one serves a specific purpose. When you're developing, the Dart VM runs in JIT mode, compiling code as it runs. This is what powers that sub-second hot reload that makes Flutter development feel so productive.&lt;/p&gt;

&lt;p&gt;For production builds, Dart's AOT compiler transforms your entire codebase into native ARM or x64 machine code. There's no runtime compilation overhead, which means fast startup times every time your app launches.&lt;/p&gt;

&lt;p&gt;The real magic of JIT is &lt;em&gt;incremental recompilation&lt;/em&gt;. When you save a file, only the functions you changed get recompiled and injected into the running VM. Your application state stays intact. Your variables, animations, and scroll positions all persist through reloads.&lt;/p&gt;

&lt;p&gt;Hot Restart is different, and you should know when to use each. Press capital &lt;code&gt;R&lt;/code&gt; and Flutter destroys the current widget tree, creates a new Dart isolate, and runs everything from &lt;code&gt;main()&lt;/code&gt; again. You lose all your state, but it takes about &lt;em&gt;2-5 seconds&lt;/em&gt; instead of hot reload's sub-second updates.&lt;/p&gt;

&lt;p&gt;AOT compilation runs a tool called &lt;code&gt;gen_snapshot&lt;/code&gt;. It analyzes your code starting from &lt;code&gt;main()&lt;/code&gt;, applies tree shaking to remove code you're not using, and generates binaries for each platform. You get faster startup, smaller apps, and code that's harder to reverse-engineer.&lt;/p&gt;

&lt;p&gt;But here's the catch: &lt;em&gt;once compiled, you can't update that Dart code without rebuilding everything and going through app stores again&lt;/em&gt;. This is the fundamental problem we're trying to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flutter's Move from Skia to Impeller Changed Everything
&lt;/h2&gt;

&lt;p&gt;The Flutter Engine is a portable C++ runtime that handles rendering, integrates with the Dart VM, and abstracts platform differences. For years, Flutter used &lt;a href="https://skia.org/" rel="noopener noreferrer"&gt;Skia&lt;/a&gt;, the same 2D graphics library that powers Chrome and Android. Skia works great for a lot of things, but it created one persistent problem: &lt;em&gt;shader compilation jank&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Here's what happens. When Skia encounters new graphical elements, things like complex gradients or blur effects or custom shaders, it has to compile GPU shaders right then and there at runtime. This compilation can take &lt;em&gt;hundreds of milliseconds&lt;/em&gt;. When you need smooth 60fps animation, each frame has to complete in 16ms. You can see the problem.&lt;/p&gt;

&lt;p&gt;Users would see visible stuttering during animations, especially the first time. No amount of optimization could fix this because it was an architectural issue.&lt;/p&gt;

&lt;p&gt;Impeller is Flutter's solution, built from the ground up specifically for Flutter's rendering patterns. The breakthrough is &lt;em&gt;AOT shader compilation&lt;/em&gt;. All shaders get compiled at build time, not runtime. No more jank.&lt;/p&gt;

&lt;p&gt;As of Flutter 3.27, &lt;a href="https://docs.flutter.dev/perf/impeller" rel="noopener noreferrer"&gt;Impeller&lt;/a&gt; is the default renderer on iOS (no fallback option) and Android API 29+. Older devices fall back to Skia. The &lt;a href="https://medium.com/@raiden.lpf666/skia-vs-impeller-a-performance-comparison-e1c7dfd9e861" rel="noopener noreferrer"&gt;benchmarks show&lt;/a&gt; impressive results: a &lt;em&gt;30% reduction in average GPU raster time&lt;/em&gt; and over &lt;em&gt;70% fewer dropped frames&lt;/em&gt; in animation-heavy apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three-Tree Architecture: How Flutter Actually Updates Your UI
&lt;/h2&gt;

&lt;p&gt;Flutter's framework maintains three parallel tree structures working together. Understanding these trees explains a lot about how Flutter performs so well.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Widget Tree&lt;/strong&gt; is your declarative UI blueprint. Widgets are immutable and lightweight. You rebuild them frequently.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Element Tree&lt;/strong&gt; manages runtime lifecycle. Elements are mutable and perform reconciliation, that diffing process you've probably heard about.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;RenderObject Tree&lt;/strong&gt; handles the actual layout, painting, and hit-testing. Creating RenderObjects is expensive, so Flutter tries to reuse them.&lt;/p&gt;

&lt;p&gt;Here's how it flows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNplVNtO4zAQ_ZWRJaRdUdg20Fu0QtqWCK1UStUWKm2yD04yNF5SO7IdoFz-HWfSC2XzkvHczpnjSV5ZolJkPrvP1VOScW1hPogkwNERBNLqNRRKSGsq1214a1CDVWWSoQGTaET5t4pMwkmV5YL4iNKS7yq8QmNLjb80Sv4z1j8uNBqVPyJoTNRSihfUhlKHYcQ2yZDwPI958mCo4puSc140QMlZolWef48YVcxCg3Zmua0LMAUh4dKxp-ggDHJcOSIOKS5FnjrvZqbfsigtzE6SUj8iDQUnJxcwoSnIvCLyZA6JHJkzQiVzsGs2qHpDkXFDrRbhGJ9gIdIlWgNOHUdvC23KeKl5kcFiHtYZMHfyEV1XWb1QpnXugGAWO5ipk0smIhfcCkXOILzmNsngqcYiqeI12HWBcAwPuP6KGsx3knyCDQ5gFwQb7KfTXCaZkEtCvA2nWBqETRvqEIxp4r2LnFWbN-L35sr2rrGC1cY7_jSbTN3aqPgfJvWWTd2aFWl1s3XshkIg7kEipiSoSxqHQ9L3IGnLoL7TaY09rg__QRaiwFxIurlROOJr5RbjGAruFpkEPYZMWLBuL50GXwWdzsMDep9UHR2oOq3JjGrWW3tD5aa0bh2rw104Ec-YG7fp-w-LqFHJXSRZgy21SJlvdYkNtkK94tWRvVZpEbOZu4WI-c6Mq4VkkXx3NQWXf5Rabcu0KpcZ8-95btypJKEvBXdDrXZeTYMNVSkt8zttj5ow_5U9M791dnba9Xr9Zqvldb1uq8HWzPe803671-13Wt3zZu-s03tvsBcCbZ722ucdr913j9dte81Og2EqrNLX9W-H_j7vH01hcuE" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNplVNtO4zAQ_ZWRJaRdUdg20Fu0QtqWCK1UStUWKm2yD04yNF5SO7IdoFz-HWfSC2XzkvHczpnjSV5ZolJkPrvP1VOScW1hPogkwNERBNLqNRRKSGsq1214a1CDVWWSoQGTaET5t4pMwkmV5YL4iNKS7yq8QmNLjb80Sv4z1j8uNBqVPyJoTNRSihfUhlKHYcQ2yZDwPI958mCo4puSc140QMlZolWef48YVcxCg3Zmua0LMAUh4dKxp-ggDHJcOSIOKS5FnjrvZqbfsigtzE6SUj8iDQUnJxcwoSnIvCLyZA6JHJkzQiVzsGs2qHpDkXFDrRbhGJ9gIdIlWgNOHUdvC23KeKl5kcFiHtYZMHfyEV1XWb1QpnXugGAWO5ipk0smIhfcCkXOILzmNsngqcYiqeI12HWBcAwPuP6KGsx3knyCDQ5gFwQb7KfTXCaZkEtCvA2nWBqETRvqEIxp4r2LnFWbN-L35sr2rrGC1cY7_jSbTN3aqPgfJvWWTd2aFWl1s3XshkIg7kEipiSoSxqHQ9L3IGnLoL7TaY09rg__QRaiwFxIurlROOJr5RbjGAruFpkEPYZMWLBuL50GXwWdzsMDep9UHR2oOq3JjGrWW3tD5aa0bh2rw104Ec-YG7fp-w-LqFHJXSRZgy21SJlvdYkNtkK94tWRvVZpEbOZu4WI-c6Mq4VkkXx3NQWXf5Rabcu0KpcZ8-95btypJKEvBXdDrXZeTYMNVSkt8zttj5ow_5U9M791dnba9Xr9Zqvldb1uq8HWzPe803671-13Wt3zZu-s03tvsBcCbZ722ucdr913j9dte81Og2EqrNLX9W-H_j7vH01hcuE%3Ftype%3Dpng" alt="Flutter rendering flow" width="537" height="1508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;setState()&lt;/code&gt; triggers a rebuild, the Element tree compares new widgets with existing ones using type and key matching. If they match, the Element gets reused and only the &lt;code&gt;RenderObject&lt;/code&gt; updates. This avoids expensive recreation.&lt;/p&gt;

&lt;p&gt;Here's something that confused me when I first learned Flutter: that &lt;code&gt;BuildContext&lt;/code&gt; passed to every &lt;code&gt;build()&lt;/code&gt; method? It's actually the &lt;code&gt;Element&lt;/code&gt; itself, just wrapped in an interface. Once you understand this architecture, you'll know why keys matter for widget identity and why &lt;code&gt;const&lt;/code&gt; constructors improve performance.&lt;/p&gt;

&lt;p&gt;The gesture system works through something called a &lt;a href="https://www.droidcon.com/2024/10/17/how-to-be-a-gladiator-in-the-gesture-arena/" rel="noopener noreferrer"&gt;&lt;em&gt;GestureArena&lt;/em&gt;&lt;/a&gt; that resolves conflicts when multiple recognizers fight for the same touch. Each recognizer can claim victory (accept) or bow out (reject). The first to complete wins exclusive handling. This is why nested scrollable areas and overlapping tap targets work so predictably.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Embedder Connects Flutter to Native Platforms
&lt;/h2&gt;

&lt;p&gt;The embedder layer is the platform-specific native application that hosts your Flutter content. It's written in Java/C++ for Android, Swift/Objective-C for iOS, and C++ for desktop platforms. Embedders provide the entry point, rendering surface, event loop, and thread management.&lt;/p&gt;

&lt;p&gt;On Android, Flutter runs as an Activity with &lt;code&gt;FlutterView&lt;/code&gt; rendering your content. &lt;a href="https://github.com/flutter/flutter/issues/150525" rel="noopener noreferrer"&gt;As of Flutter 3.32&lt;/a&gt;, the UI and platform threads are merged for better performance. iOS hosts Flutter in a &lt;code&gt;FlutterViewController&lt;/code&gt; using Metal for rendering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.flutter.dev/platform-integration/platform-channels" rel="noopener noreferrer"&gt;Platform channels&lt;/a&gt; let you communicate between Dart and native code through three patterns:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MethodChannel&lt;/code&gt; handles request-response calls to native methods. &lt;code&gt;EventChannel&lt;/code&gt; streams data from native to Dart, useful for sensors and real-time updates. &lt;code&gt;BasicMessageChannel&lt;/code&gt; enables bidirectional asynchronous messaging.&lt;/p&gt;

&lt;p&gt;For complex interop, &lt;a href="https://dart.dev/guides/libraries/c-interop" rel="noopener noreferrer"&gt;FFI (Foreign Function Interface)&lt;/a&gt; provides synchronous calls to C-compatible code with better performance, though it adds complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Hot Reload Can and Can't Do
&lt;/h2&gt;

&lt;p&gt;Understanding how hot reload actually works reveals both its capabilities and its limits. When you press &lt;code&gt;r&lt;/code&gt;, your development machine scans for changed code, recompiles the affected libraries plus your main library, generates Dart kernel files (an intermediate representation), and sends them to your device's Dart VM. The VM reloads the libraries, and Flutter triggers a complete rebuild and repaint of existing widgets.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/dart-lang/sdk/blob/main/runtime/docs/hot-reload.md" rel="noopener noreferrer"&gt;Dart VM wiki&lt;/a&gt; describes this as &lt;em&gt;pervasive late-binding&lt;/em&gt;. The program behaves as if method lookup happens at every call site. Field values stay preserved, though. Changing an initializer doesn't affect already-initialized variables. Closures capture their function when created and won't pick up changes.&lt;/p&gt;

&lt;p&gt;Hot reload can't handle certain changes. You'll need a  hot restart instead for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enum-to-class conversions or generic type modifications&lt;/li&gt;
&lt;li&gt;Changes to &lt;code&gt;main()&lt;/code&gt; or &lt;code&gt;initState()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Static field initializer changes&lt;/li&gt;
&lt;li&gt;Native code modifications (these always require a full app restart)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Think About Testing Your Flutter App
&lt;/h2&gt;

&lt;p&gt;Flutter's testing framework follows the testing pyramid principle: lots of fast unit tests, fewer widget tests, and minimal integration tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit tests&lt;/strong&gt; use the &lt;a href="https://pub.dev/packages/test" rel="noopener noreferrer"&gt;test&lt;/a&gt; package to verify isolated functions and classes. You should mock external dependencies using packages like &lt;a href="https://pub.dev/packages/mockito" rel="noopener noreferrer"&gt;Mockito&lt;/a&gt;. These tests run in milliseconds and catch logic errors early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Widget tests&lt;/strong&gt; use &lt;a href="https://api.flutter.dev/flutter/flutter_test/" rel="noopener noreferrer"&gt;flutter_test&lt;/a&gt; and the &lt;code&gt;WidgetTester&lt;/code&gt; class to render widgets without a physical device. Key methods include &lt;code&gt;pumpWidget()&lt;/code&gt; to build the widget tree, &lt;code&gt;pump()&lt;/code&gt; to advance by one frame after state changes, and &lt;code&gt;pumpAndSettle()&lt;/code&gt; to wait for all animations to complete. The &lt;code&gt;find&lt;/code&gt; API locates widgets by text, type, key, or icon. Matchers like &lt;code&gt;findsOneWidget&lt;/code&gt; and &lt;code&gt;findsNothing&lt;/code&gt; verify your results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration tests&lt;/strong&gt; use the &lt;a href="https://docs.flutter.dev/testing/integration-tests" rel="noopener noreferrer"&gt;integration_test&lt;/a&gt; package (which replaced &lt;code&gt;flutter_driver&lt;/code&gt;) to test complete app flows on real devices. Tests run through &lt;code&gt;IntegrationTestWidgetsFlutterBinding&lt;/code&gt; and can execute on &lt;a href="https://firebase.google.com/docs/test-lab" rel="noopener noreferrer"&gt;Firebase Test Lab&lt;/a&gt; for device farm testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem with AOT Compilation
&lt;/h2&gt;

&lt;p&gt;The build process, whether you run &lt;code&gt;flutter build apk&lt;/code&gt; or &lt;code&gt;flutter build ipa&lt;/code&gt;, invokes AOT compilation. It transforms your Dart source into native machine code. Tree shaking removes unreachable code. &lt;a href="https://developer.android.com/studio/build/shrink-code" rel="noopener noreferrer"&gt;R8&lt;/a&gt; shrinks Java/Kotlin code on Android. The final binary gets signed for distribution.&lt;/p&gt;

&lt;p&gt;Build times typically range from &lt;em&gt;3-8 minutes for Android&lt;/em&gt; and &lt;em&gt;10-25 minutes for iOS&lt;/em&gt;. If you use Cloud Firestore, add significant time because of its 500K+ lines of C++ code.&lt;/p&gt;

&lt;p&gt;This creates Flutter's fundamental limitation: no code push capability. React Native developers can update JavaScript bundles at runtime through services like &lt;a href="https://microsoft.github.io/code-push/" rel="noopener noreferrer"&gt;Microsoft's CodePush&lt;/a&gt;. Flutter's compiled Dart code is static machine code. There's no runtime interpreter in release builds. Once you publish an app, fixing bugs requires a full app store submission. That's typically 1-7 days for Apple review and hours to a day for Google Play.&lt;/p&gt;

&lt;p&gt;CI/CD pipelines face their own challenges. iOS requires macOS runners for building. Code signing gets complex with certificates, provisioning profiles, and ExportOptions.plist files. You have to maintain multiple platform builds at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Shorebird Solves the Update Problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://shorebird.dev/" rel="noopener noreferrer"&gt;Shorebird&lt;/a&gt;, founded by Eric Seidel (one of Flutter's original creators), solves this through sophisticated engine modifications. The platform maintains forks of four key Flutter repositories: &lt;code&gt;buildroot&lt;/code&gt;, &lt;code&gt;engine&lt;/code&gt;, &lt;code&gt;flutter&lt;/code&gt;, and &lt;code&gt;dart-lang/sdk&lt;/code&gt;. When you install Shorebird, it provides its own Flutter and Dart copies that produce &lt;a href="https://docs.shorebird.dev/code-push/" rel="noopener noreferrer"&gt;Shorebird-enabled binaries&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The technical breakthrough required building a &lt;em&gt;custom Dart interpreter&lt;/em&gt; rather than using Dart's JIT. Apple's developer agreement requires interpreted code for OTA updates. It prohibits JIT compilation. Shorebird's interpreter runs approximately &lt;em&gt;100x slower&lt;/em&gt; than AOT code, but here's the clever part: only the changed code uses the interpreter.&lt;/p&gt;

&lt;p&gt;A novel &lt;em&gt;linker&lt;/em&gt; phase analyzes two Dart programs (your release and your patch), finds maximal similarity, and determines per-function whether to use the original binary or interpreter. Typically &lt;em&gt;98%+ of your patched code&lt;/em&gt; runs from the original binary at full speed.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.shorebird.dev/code-push/guides/development-workflow/" rel="noopener noreferrer"&gt;workflow&lt;/a&gt; integrates seamlessly with your existing development process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shorebird release android    &lt;span class="c"&gt;# Build and register release&lt;/span&gt;
&lt;span class="c"&gt;# Submit to app stores...&lt;/span&gt;
&lt;span class="c"&gt;# Fix bug in Dart code...&lt;/span&gt;
shorebird patch android      &lt;span class="c"&gt;# Create and deploy patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.shorebird.dev/code-push/patch/" rel="noopener noreferrer"&gt;Patches&lt;/a&gt; use &lt;em&gt;binary diffing&lt;/em&gt; on Android. This produces patches as small as a few kilobytes, sometimes just hundreds of bytes for minor changes. iOS patches are larger but now also use differential updates. Users receive patches on their next app restart. There's automatic &lt;a href="https://docs.shorebird.dev/code-push/rollback/" rel="noopener noreferrer"&gt;rollback protection&lt;/a&gt; if a patch fails to launch.&lt;/p&gt;

&lt;p&gt;Critically, Shorebird maintains app store compliance. Patches can only modify Dart code, not native code, the Flutter engine, or assets. Updates can't change your app's primary purpose. This aligns with both &lt;a href="https://docs.shorebird.dev/code-push/guides/stores/app-store/" rel="noopener noreferrer"&gt;Apple&lt;/a&gt; and &lt;a href="https://docs.shorebird.dev/code-push/guides/stores/play-store/" rel="noopener noreferrer"&gt;Google&lt;/a&gt; guidelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Should Know About Flutter Development in 2026
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Flutter 3.38.5&lt;/strong&gt; (December 2025) is the current stable release, bundled with &lt;strong&gt;Dart 3.10.4&lt;/strong&gt;. You'll need Java 17 minimum for Android, iOS 13+ minimum, and Android 16KB page size support for Google Play compliance. Impeller is fully default across iOS and Android API 29+.&lt;/p&gt;

&lt;p&gt;For static analysis, configure your &lt;code&gt;analysis_options.yaml&lt;/code&gt; to include &lt;code&gt;package:flutter_lints/flutter.yaml&lt;/code&gt; and enable strict mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;analyzer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;strict-casts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;strict-inference&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;flutter analyze&lt;/code&gt; before commits. Use &lt;code&gt;dart fix --apply&lt;/code&gt; to automatically resolve deprecated API usage. For deeper analysis, &lt;a href="https://dcm.dev/" rel="noopener noreferrer"&gt;DCM (Dart Code Metrics)&lt;/a&gt; provides complexity metrics and unused code detection.&lt;/p&gt;

&lt;p&gt;State management in 2025-2026 favors &lt;em&gt;&lt;a href="https://riverpod.dev/" rel="noopener noreferrer"&gt;Riverpod 3&lt;/a&gt;&lt;/em&gt; for new projects because of compile-time safety and modular architecture. &lt;em&gt;&lt;a href="https://bloclibrary.dev/" rel="noopener noreferrer"&gt;Bloc&lt;/a&gt;&lt;/em&gt; works well for enterprise applications requiring strict separation of concerns. &lt;em&gt;&lt;a href="https://pub.dev/packages/provider" rel="noopener noreferrer"&gt;Provider&lt;/a&gt;&lt;/em&gt; is good for simpler applications. &lt;a href="https://pub.dev/packages/signals" rel="noopener noreferrer"&gt;Flutter Signals&lt;/a&gt; has emerged as an option for local reactive state.&lt;/p&gt;

&lt;p&gt;I recommend a feature-first project structure for medium-to-large applications. Split your code into &lt;code&gt;core/&lt;/code&gt;, &lt;code&gt;features/&lt;/code&gt;, and &lt;code&gt;services/&lt;/code&gt; directories, with each feature containing its own data, domain, and presentation layers. Use &lt;code&gt;const&lt;/code&gt; constructors liberally. Keep widget trees shallow. Profile regularly with &lt;a href="https://docs.flutter.dev/tools/devtools/overview" rel="noopener noreferrer"&gt;DevTools&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Flutter's architecture delivers native performance through AOT compilation while maintaining development velocity through JIT-powered hot reload. The shift from Skia to Impeller eliminates that historical shader jank problem, making 60fps animations reliably smooth. The three-tree rendering architecture and platform embedder design enable true cross-platform code sharing without sacrificing native integration.&lt;/p&gt;

&lt;p&gt;The most significant evolution for production Flutter teams is &lt;a href="https://docs.shorebird.dev/code-push/" rel="noopener noreferrer"&gt;Shorebird's code push capability&lt;/a&gt;. By building a custom interpreter and novel per-function linker, &lt;a href="https://docs.shorebird.dev/code-push/system-architecture/" rel="noopener noreferrer"&gt;Shorebird solves the immutability problem&lt;/a&gt; while maintaining app store compliance. You can deploy bug fixes in hours rather than days. Combined with &lt;a href="https://docs.shorebird.dev/ci/" rel="noopener noreferrer"&gt;Shorebird CI's&lt;/a&gt; zero-config testing and modern static analysis practices, Flutter teams can ship confidently while retaining the ability to respond quickly to production issues.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>flutter</category>
      <category>dart</category>
      <category>shorebird</category>
    </item>
    <item>
      <title>Async/Await vs Promises: A Simple Guide for JavaScript Beginners</title>
      <dc:creator>Kumar Harsh</dc:creator>
      <pubDate>Fri, 13 Dec 2024 06:20:48 +0000</pubDate>
      <link>https://dev.to/kumarharsh/asyncawait-vs-promises-a-simple-guide-for-javascript-beginners-4f6g</link>
      <guid>https://dev.to/kumarharsh/asyncawait-vs-promises-a-simple-guide-for-javascript-beginners-4f6g</guid>
      <description>&lt;p&gt;Have you ever felt like you’re waiting in line at a coffee shop for JavaScript to fetch your latte? Asynchronous programming can often feel like that—multiple orders being processed at the same time can leave you stuck waiting. Fortunately, tools like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" rel="noopener noreferrer"&gt;&lt;em&gt;Promises&lt;/em&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises#async_and_await" rel="noopener noreferrer"&gt;&lt;em&gt;async/await&lt;/em&gt;&lt;/a&gt; ensure the process stays smooth and efficient, letting your code keep moving without delays.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll break down how Promises work, why async/await was introduced, and how it simplifies writing asynchronous code. Whether you’re a beginner trying to grasp these concepts or looking for clarity on when to use each approach, this article will help you master the basics.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Promises?
&lt;/h2&gt;

&lt;p&gt;Promises are a foundational concept in JavaScript for handling asynchronous operations. At their core, a Promise represents a value that might be available &lt;em&gt;now&lt;/em&gt;, &lt;em&gt;later&lt;/em&gt;, or &lt;em&gt;never&lt;/em&gt;. Think of it like a tracking number for a package: while you don’t have the package yet, the tracking number gives you confidence that it’s on its way (or lets you know if something went wrong).&lt;/p&gt;

&lt;p&gt;Building upon the "now, later, or never" narrative, a Promise actually operates in one of three states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pending&lt;/strong&gt;: The asynchronous operation hasn’t been completed yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fulfilled&lt;/strong&gt;: The operation was completed successfully, and the Promise now holds the result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rejected&lt;/strong&gt;: Something went wrong, and the Promise provides an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Creating and working with Promises involves &lt;a href="https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise-objects" rel="noopener noreferrer"&gt;a simple API&lt;/a&gt;. Here’s how you can define a Promise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;JavaScript Basics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Simulates a successful operation&lt;/span&gt;
    &lt;span class="c1"&gt;// reject("Error: Unable to fetch data"); // Simulates a failure&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To handle the result, you can chain &lt;code&gt;.then()&lt;/code&gt;, &lt;code&gt;.catch()&lt;/code&gt;, and &lt;code&gt;.finally()&lt;/code&gt; methods to the Promise object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fetchData&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Data received:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Operation complete.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The callback in the &lt;code&gt;then()&lt;/code&gt; method is executed when the Promise resolves with a successful result. The callback in the &lt;code&gt;.catch()&lt;/code&gt; method is executed when the Promise resolves with a failed result, and the callback in the &lt;code&gt;finally()&lt;/code&gt; method is executed after the Promise has resolved, irrespective of the result of the resolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benefits of Promises
&lt;/h2&gt;

&lt;p&gt;Promises provide a cleaner alternative to deeply nested callbacks, often referred to as “callback hell.” Instead of stacking callbacks, Promises allow chaining, making the flow of operations easier to follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;doTask1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;doTask2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;doTask3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what this same code would have looked like if it had been written using traditional callbacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;doTask1&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;doTask2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;doTask3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Final result:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confusing, isn't it? This is why Promises were a game-changer in JavaScript coding standards when they were introduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shortcomings of Promises
&lt;/h2&gt;

&lt;p&gt;While Promises greatly improved upon traditional callback functions, they did not come without their own unique challenges. Despite their benefits, they can become unwieldy in complex scenarios, resulting in verbose code and debugging difficulties.&lt;/p&gt;

&lt;p&gt;Even with &lt;code&gt;.then()&lt;/code&gt; chaining, Promises can result in cluttered code when dealing with multiple asynchronous operations. For example, managing sequential operations with &lt;code&gt;.then()&lt;/code&gt; blocks and error handling using &lt;code&gt;.catch()&lt;/code&gt; can feel repetitive and harder to follow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;doTask1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;doTask2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Task1Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred in task 1:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;doTask3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Task2Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred in task 2:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred in the chain:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While cleaner than nested callbacks, the chaining syntax is still verbose, especially when detailed custom error-handling logic is required. Moreover, forgetting to add a &lt;code&gt;.catch()&lt;/code&gt; at the end of a chain can lead to silent failures, making debugging tricky.&lt;/p&gt;

&lt;p&gt;Furthermore, stack traces in Promises are not as intuitive as those in synchronous code. When an error occurs, the stack trace may not clearly indicate where the issue originated in your asynchronous flow.&lt;/p&gt;

&lt;p&gt;Lastly, although Promises help reduce callback hell, they can still result in complexity when tasks are interdependent. Nested &lt;code&gt;.then()&lt;/code&gt; blocks can creep back in for certain use cases, bringing back some of the readability challenges they were meant to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter async/await
&lt;/h2&gt;

&lt;p&gt;Asynchronous programming in JavaScript took a giant leap forward with the &lt;a href="https://www.sitepoint.com/es2017-whats-new/" rel="noopener noreferrer"&gt;introduction of async/await in ES2017 (ES8)&lt;/a&gt;. Built on top of Promises, async/await allows developers to write asynchronous code that looks and behaves more like synchronous code. This makes it a real game-changer for improving readability, simplifying error handling, and reducing verbosity.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is async/await?
&lt;/h3&gt;

&lt;p&gt;Async/await is a syntax designed to make asynchronous code easier to understand and maintain.&lt;/p&gt;

&lt;p&gt;The async keyword is used to declare a function that always returns a Promise. Within this function, the await keyword pauses execution until a Promise is resolved or rejected. This results in a flow that feels linear and intuitive, even for complex asynchronous operations.&lt;/p&gt;

&lt;p&gt;Here’s an example of how async/await simplifies the same code example you saw above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executeTasks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;doTask1&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;doTask2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;doTask3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All tasks completed successfully:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Task1Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred in task 1:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Task2Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred in task 2:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred in the chain:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;executeTasks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Async/await eliminates the need for &lt;code&gt;.then()&lt;/code&gt; chains, allowing code to flow sequentially. This makes it easier to follow the logic, especially for tasks that need to be executed one after another. &lt;/p&gt;

&lt;p&gt;With Promises, errors must be caught at every level of the chain using &lt;code&gt;.catch()&lt;/code&gt;. Async/await, on the other hand, consolidates error handling using try/catch, reducing repetition and improving clarity.&lt;/p&gt;

&lt;p&gt;Async/await produces more intuitive stack traces than Promises. When an error occurs, the trace reflects the actual function call hierarchy, making debugging less frustrating. On the whole, async/await feels more "natural" because it aligns with how synchronous code is written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing Promises and async/await
&lt;/h2&gt;

&lt;p&gt;As you've already seen, Async/await shines when it comes to readability, especially for sequential operations. Promises, with their &lt;code&gt;.then()&lt;/code&gt; and &lt;code&gt;.catch()&lt;/code&gt; chaining, can quickly become verbose or complex. In contrast, async/await code is easier to follow as it mimics a synchronous structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flexibility
&lt;/h3&gt;

&lt;p&gt;Promises still have their place, particularly for concurrent tasks. Methods like &lt;code&gt;Promise.all()&lt;/code&gt; and &lt;code&gt;Promise.race()&lt;/code&gt; are more efficient for running multiple asynchronous operations in parallel. Async/await can handle such cases too, but it requires extra logic to achieve the same result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A Promise.all() example&lt;/span&gt;
&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;task1&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;task2&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;task3&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All tasks completed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error handling
&lt;/h3&gt;

&lt;p&gt;While centralized error handling with a single &lt;code&gt;.catch()&lt;/code&gt; works well for linear chains of Promises, &lt;a href="https://stackoverflow.com/a/26077620" rel="noopener noreferrer"&gt;it is recommended&lt;/a&gt; to use distributed &lt;code&gt;.catch&lt;/code&gt; calls for different error types across chains for best readability.&lt;/p&gt;

&lt;p&gt;On the other hand, a try/catch block offers a more natural structure for handling errors, especially when dealing with sequential tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;In terms of performance, async/await is essentially equivalent to Promises since it is built on top of them. However, for tasks requiring concurrency, &lt;code&gt;Promise.all()&lt;/code&gt; can be more efficient because it allows multiple Promises to execute in parallel, failing fast if any Promise rejects.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use Which
&lt;/h3&gt;

&lt;p&gt;If your tasks involve a lot of concurrent operations, such as fetching data from multiple APIs simultaneously, Promises are most probably the better choice. If your asynchronous code does not involve a lot of chaining, Promises would be well-suited in that situation as well because of its simplicity.&lt;/p&gt;

&lt;p&gt;On the other hand, async/await excels in situations where a lot of tasks need to be executed sequentially or when readability and maintainability are priorities. For example, if you have a series of dependent operations, such as fetching data, transforming it, and saving it, async/await offers a clean and synchronous structure. This makes it easier to follow the flow of operations and simplifies centralized error handling with try/catch blocks. Async/await is especially useful for beginners or teams prioritizing readable code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;JavaScript offers two powerful tools for managing asynchronous operations: Promises and async/await. Promises revolutionized the way developers handle asynchronous tasks, resolving issues like callback hell and enabling chaining. Async/await builds on Promises, providing a cleaner syntax that feels more natural and intuitive, especially for sequential tasks.&lt;/p&gt;

&lt;p&gt;Now that you’ve explored both approaches, you’re equipped to choose the best one for your needs. Try converting a Promise-based function to async/await and observe the difference in readability! &lt;/p&gt;

&lt;p&gt;For more information, check out the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" rel="noopener noreferrer"&gt;MDN Promise documentation&lt;/a&gt; or experiment with an interactive coding sandbox!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
