<?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: Nikita Sin</title>
    <description>The latest articles on DEV Community by Nikita Sin (@lesleysin).</description>
    <link>https://dev.to/lesleysin</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%2F2977251%2Fefafd481-4006-4d56-b741-619151c224e7.jpeg</url>
      <title>DEV Community: Nikita Sin</title>
      <link>https://dev.to/lesleysin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lesleysin"/>
    <language>en</language>
    <item>
      <title>DuitDataSource: The Data Layer That Quietly Changed Everything in Duit</title>
      <dc:creator>Nikita Sin</dc:creator>
      <pubDate>Fri, 27 Mar 2026 07:36:13 +0000</pubDate>
      <link>https://dev.to/lesleysin/duitdatasource-the-data-layer-that-quietly-changed-everything-in-duit-1i44</link>
      <guid>https://dev.to/lesleysin/duitdatasource-the-data-layer-that-quietly-changed-everything-in-duit-1i44</guid>
      <description>&lt;p&gt;1000 widget update iterations: about 1.9 seconds with the old attribute-based approach, 114 ms with the newer one.&lt;/p&gt;

&lt;p&gt;That gap is the reason this article exists.&lt;/p&gt;

&lt;p&gt;In Duit, widget updates used to revolve around attribute classes: parse JSON into Dart objects, create new attribute instances for every update, then merge old and new state to produce the final result. It was structured. It was explicit. In a hot path, especially during animations, it was also too expensive.&lt;/p&gt;

&lt;p&gt;Each update meant more parsing, more copying, more object allocation, and more boilerplate than the framework was getting back in value. In the most expensive scenarios, that translated into more GC pressure and slower frame preparation.&lt;/p&gt;

&lt;p&gt;The improvement did not come from one clever micro-optimization. It came from changing the role of data in the framework.&lt;/p&gt;

&lt;p&gt;This article explains why &lt;code&gt;DuitDataSource&lt;/code&gt; is not just a typed wrapper around &lt;code&gt;Map&amp;lt;String, dynamic&amp;gt;&lt;/code&gt;, but the runtime layer that now handles parsing, partial updates, memoization, and a large share of the framework's hot-path optimization work.&lt;/p&gt;

&lt;p&gt;For a bit of context: Duit is a BDUI framework for Flutter. It lets teams describe UI remotely and turn structured payloads into native Flutter widgets, actions, commands, and runtime behavior on the client.&lt;/p&gt;

&lt;p&gt;That solves a very practical set of problems. It helps ship UI changes faster, reduces the amount of client-side release work for presentation-level updates, and gives smaller teams access to a BDUI-style architecture without having to build an entire internal platform from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The old problem was not "JSON"
&lt;/h2&gt;

&lt;p&gt;The obvious explanation would be: "JSON is dynamic, typed attribute classes are safer, therefore classes are the right abstraction."&lt;/p&gt;

&lt;p&gt;That sounds good until you have to live with the consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every widget needs a dedicated attribute model&lt;/li&gt;
&lt;li&gt;every update creates intermediate objects&lt;/li&gt;
&lt;li&gt;merge logic becomes both repetitive and fragile&lt;/li&gt;
&lt;li&gt;animations magnify the cost because the same properties are touched again and again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem was not that the framework started from &lt;code&gt;Map&amp;lt;String, dynamic&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem was converting that map into too many short-lived objects too early, then paying the price for that decision on every update.&lt;/p&gt;

&lt;p&gt;So I went back to the thing I originally tried to avoid: a plain &lt;code&gt;Map&amp;lt;String, dynamic&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Not raw. Not unstructured. Not "just trust the payload".&lt;/p&gt;

&lt;p&gt;Wrapped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;DuitDataSource&lt;/code&gt; exists
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DuitDataSource&lt;/code&gt; is an extension type over &lt;code&gt;Map&amp;lt;String, dynamic&amp;gt;&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="n"&gt;extension&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="nf"&gt;DuitDataSource&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="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="kd"&gt;implements&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="c1"&gt;// typed accessors and converters&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It keeps the cheap and flexible storage model of a map, but exposes strongly typed getters and conversion methods for Flutter and Dart values: colors, durations, alignments, curves, borders, actions, animation settings, command payloads, and many other property types.&lt;/p&gt;

&lt;p&gt;So the framework no longer needs a dedicated "attributes class" per widget just to say "this JSON field is a &lt;code&gt;Color&lt;/code&gt;, this one is an &lt;code&gt;EdgeInsets&lt;/code&gt;, this one is a &lt;code&gt;Duration&lt;/code&gt;."&lt;/p&gt;

&lt;p&gt;Instead, &lt;code&gt;DuitDataSource&lt;/code&gt; becomes a typed access layer over the payload that already exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What role it plays in the current project
&lt;/h2&gt;

&lt;p&gt;Today &lt;code&gt;DuitDataSource&lt;/code&gt; is not a small helper. It is one of the core runtime abstractions in Duit.&lt;/p&gt;

&lt;p&gt;It plays at least four different roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Boundary between remote JSON and native Flutter types
&lt;/h2&gt;

&lt;p&gt;This is the most visible role.&lt;/p&gt;

&lt;p&gt;Transport code receives JSON. Widgets, commands, and actions need Flutter-native values. &lt;code&gt;DuitDataSource&lt;/code&gt; is the boundary where that transformation happens.&lt;/p&gt;

&lt;p&gt;For example, transport decoding can optionally use &lt;code&gt;DuitDataSource.jsonReviver&lt;/code&gt; during &lt;code&gt;jsonDecode&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;reviver:&lt;/span&gt; &lt;span class="n"&gt;DuitDataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jsonReviver&lt;/span&gt;&lt;span class="p"&gt;,&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then higher layers wrap the payload with &lt;code&gt;DuitDataSource&lt;/code&gt; and read typed values instead of manually parsing maps all over the codebase:&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;final&lt;/span&gt; &lt;span class="kn"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DuitDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commandData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BottomSheetCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="kn"&gt;source&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="kn"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tryParseColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="s"&gt;"backgroundColor"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;shape:&lt;/span&gt; &lt;span class="kn"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shapeBorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="s"&gt;"shape"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;clipBehavior:&lt;/span&gt; &lt;span class="kn"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clipBehavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="s"&gt;"clipBehavior"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;onClose:&lt;/span&gt; &lt;span class="kn"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"onClose"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;action:&lt;/span&gt; &lt;span class="n"&gt;OverlayAction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="s"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The effect is simple and important: parsing logic stops leaking into every command, widget, and adapter.&lt;/p&gt;

&lt;p&gt;The framework gets one place where data is interpreted, and the rest of the runtime gets to consume typed values.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Update substrate for widget changes and animations
&lt;/h2&gt;

&lt;p&gt;This is where the "it is just a typed map wrapper" explanation stops being enough.&lt;/p&gt;

&lt;p&gt;In the current project, &lt;code&gt;DuitDataSource&lt;/code&gt; is also the object that updates flow through.&lt;/p&gt;

&lt;p&gt;Animated properties are merged directly into the same data source:&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;mixin&lt;/span&gt; &lt;span class="nc"&gt;AnimatedAttributes&lt;/span&gt; &lt;span class="kd"&gt;on&lt;/span&gt; &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;DuitDataSource&lt;/span&gt; &lt;span class="n"&gt;mergeWithDataSource&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="n"&gt;DuitDataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&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;parentId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parentBuilderId&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;parentId&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dataSource&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;animationContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DuitAnimationContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maybeOf&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animationContext&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dataSource&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;animationContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parentId&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;parentId&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;dataSource&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;affectedProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;affectedProperties&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;affectedProps&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;affectedProps&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;return&lt;/span&gt; &lt;span class="n"&gt;dataSource&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;animatedProperties&lt;/span&gt; &lt;span class="o"&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="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;{};&lt;/span&gt;

    &lt;span class="k"&gt;for&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;prop&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;affectedProps&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;animationContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;streams&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;value&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;value&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="n"&gt;animatedProperties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&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;return&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animatedProperties&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;DuitDataSource&lt;/code&gt; is no longer only the thing that reads properties. It is also the thing that receives partial updates, applies merges, and becomes the mutable substrate for the final widget state.&lt;/p&gt;

&lt;p&gt;This is why it matters so much in animation-heavy scenarios. The framework does not need to keep manufacturing replacement attribute objects just to override one or two fields for the next frame.&lt;/p&gt;

&lt;p&gt;It can merge the changed values into the current data structure and move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Memoization layer, not just a parser
&lt;/h2&gt;

&lt;p&gt;This is the role that ended up being the most important.&lt;/p&gt;

&lt;p&gt;At first glance, a method like &lt;code&gt;tryParseColor()&lt;/code&gt; looks like a parser helper. In reality, it is doing something more valuable: it memoizes the parsed result.&lt;/p&gt;

&lt;p&gt;After the first successful conversion, the native object is written back into the underlying map:&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;Color&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;tryParseColor&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlutterPropertyKeys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;warmUp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_readProp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;warmUp&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;value&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Color&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;value&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;value&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&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;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_colorFromHexString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_colorFromList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;defaultValue&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 same pattern appears across many accessors: colors, durations, enums, alignments, sizes, actions, and more.&lt;/p&gt;

&lt;p&gt;That changes the economics of repeated reads.&lt;/p&gt;

&lt;p&gt;The first access pays the parsing cost. The next access often becomes a cheap "already typed, return as is" path. In a static payload, that is nice. In repeated rebuilds and animations, that is a major win.&lt;/p&gt;

&lt;p&gt;That is why I would describe the current role of &lt;code&gt;DuitDataSource&lt;/code&gt; like this:&lt;/p&gt;

&lt;p&gt;It is not primarily a JSON parser. It is a memoizing typed data layer for a remote UI runtime.&lt;/p&gt;

&lt;p&gt;That distinction matters because it explains both the API and the performance profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. A deliberate performance lever
&lt;/h2&gt;

&lt;p&gt;Once &lt;code&gt;DuitDataSource&lt;/code&gt; became the center of property access, it also became the right place for low-level optimization.&lt;/p&gt;

&lt;p&gt;That is where optimizations like these start to pay off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;replacing attribute object copying with map merge semantics&lt;/li&gt;
&lt;li&gt;reusing already parsed native values&lt;/li&gt;
&lt;li&gt;using lookup tables for enum-like conversions&lt;/li&gt;
&lt;li&gt;keeping hot functions small enough to be good candidates for &lt;code&gt;@pragma('vm:prefer-inline')&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;jsonReviver&lt;/code&gt; and warm-up dispatch to front-load selected conversions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because &lt;code&gt;DuitDataSource&lt;/code&gt; methods are not called once in a while. They are called everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;while decoding transport responses&lt;/li&gt;
&lt;li&gt;while creating commands&lt;/li&gt;
&lt;li&gt;while building widgets&lt;/li&gt;
&lt;li&gt;while resolving styles&lt;/li&gt;
&lt;li&gt;while applying animated updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a function in that path becomes 15% faster, it is not an academic improvement. It compounds.&lt;/p&gt;

&lt;p&gt;And if a function in that path avoids allocations entirely, the gain is not just raw CPU time. It is also lower GC activity and more predictable runtime behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why extension types were the right tool
&lt;/h2&gt;

&lt;p&gt;Without extension types, this design would be much less attractive.&lt;/p&gt;

&lt;p&gt;Using a wrapper class would introduce more ceremony and potentially more overhead. Using a raw map directly would destroy the ergonomics and safety of the API.&lt;/p&gt;

&lt;p&gt;Extension types make it possible to keep the data representation simple while still exposing a rich, typed interface.&lt;/p&gt;

&lt;p&gt;That is the real trick behind &lt;code&gt;DuitDataSource&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The representation stays simple, while the API remains typed and the hot path stays cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changed for the framework
&lt;/h2&gt;

&lt;p&gt;The benefits go beyond benchmark charts.&lt;/p&gt;

&lt;p&gt;Moving the framework around &lt;code&gt;DuitDataSource&lt;/code&gt; changed several things at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;less widget-specific boilerplate&lt;/li&gt;
&lt;li&gt;fewer fragile merge/copy implementations&lt;/li&gt;
&lt;li&gt;fewer intermediate objects in the update pipeline&lt;/li&gt;
&lt;li&gt;simpler APIs for commands, custom widgets, and model construction&lt;/li&gt;
&lt;li&gt;a better foundation for tooling built around Duit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also why &lt;code&gt;DuitDataSource&lt;/code&gt; matters to the future of the project, not just to one optimization pass.&lt;/p&gt;

&lt;p&gt;When the core runtime speaks a consistent typed-map language, other parts of the ecosystem get easier to build. Tooling, editor integrations, debugging helpers, visual builders, and migration utilities all benefit from that simplification.&lt;/p&gt;

&lt;p&gt;From that perspective, &lt;code&gt;DuitDataSource&lt;/code&gt; is not a utility type.&lt;/p&gt;

&lt;p&gt;It is the data contract the runtime now organizes itself around.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger lesson
&lt;/h2&gt;

&lt;p&gt;The interesting part of this story is not "maps are faster than classes."&lt;/p&gt;

&lt;p&gt;That would be too shallow, and often false outside the specific workload.&lt;/p&gt;

&lt;p&gt;The real lesson is that the best abstraction is the one that matches the runtime behavior you actually need.&lt;/p&gt;

&lt;p&gt;In Duit, the hot path was not "create beautiful immutable attribute models." The hot path was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;receive remote payload&lt;/li&gt;
&lt;li&gt;read a lot of properties repeatedly&lt;/li&gt;
&lt;li&gt;update only a small subset of them frequently&lt;/li&gt;
&lt;li&gt;avoid extra allocations while doing it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once I looked at the problem through that lens, &lt;code&gt;DuitDataSource&lt;/code&gt; stopped being an implementation detail and became the right architectural center.&lt;/p&gt;

&lt;p&gt;That is its role in the project today.&lt;/p&gt;

&lt;p&gt;It is the layer that turns dynamic payloads into typed Flutter data, the object that absorbs partial updates, the cache that avoids reparsing work, and the lever that lets performance work scale across the entire runtime.&lt;/p&gt;

&lt;p&gt;Not bad for something that started as "maybe I should just use a map."&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaway
&lt;/h2&gt;

&lt;p&gt;If you are building a remote UI system, a config-driven runtime, or any framework that repeatedly interprets structured data, do not ask only:&lt;/p&gt;

&lt;p&gt;"How do I make this typed?"&lt;/p&gt;

&lt;p&gt;Also ask:&lt;/p&gt;

&lt;p&gt;"Where will this data live after the first parse?"&lt;br&gt;
"How many times will I read it again?"&lt;br&gt;
"Can the same structure be both the source of truth and the cache?"&lt;/p&gt;

&lt;p&gt;In Duit, that set of questions led to &lt;code&gt;DuitDataSource&lt;/code&gt;. Once it did, a lot of other pieces started to fall into place.&lt;/p&gt;

&lt;p&gt;If you want to dig into the implementation, start here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Duit-Foundation" rel="noopener noreferrer"&gt;Duit Foundation on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pub.dev/publishers/dev.duit.pro/packages" rel="noopener noreferrer"&gt;Duit on Pub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>flutter</category>
      <category>performance</category>
      <category>ui</category>
    </item>
    <item>
      <title>You didn’t understand Extension Types in Dart</title>
      <dc:creator>Nikita Sin</dc:creator>
      <pubDate>Thu, 03 Jul 2025 07:10:00 +0000</pubDate>
      <link>https://dev.to/lesleysin/you-didnt-understand-extension-types-in-dart-ik</link>
      <guid>https://dev.to/lesleysin/you-didnt-understand-extension-types-in-dart-ik</guid>
      <description>&lt;p&gt;Dart continues to evolve, gaining new language features. While extension methods have become an everyday tool, extension types remain in the shadows — completely undeservedly. Why has such a powerful mechanism been underappreciated? In what cases is it truly indispensable? &lt;/p&gt;

&lt;p&gt;In this article, we’ll reflect on Dart’s evolution in the context of static type extensions and explore practical use cases for extension types.&lt;/p&gt;

&lt;p&gt;Hi! My name is Nikita Sin. I’m a lead Flutter developer at betting company BetBoom, the author of the BDUI framework for Flutter — &lt;a href="https://duit.pro/en/" rel="noopener noreferrer"&gt;Duit&lt;/a&gt;. I’m also the leader of the mobile developers’ community Mobile Assembly | Kaliningrad.&lt;/p&gt;

&lt;p&gt;I’d bet that you use static type extensions every day! But, as my experience studying open-source library code and interacting with other developers shows, many Dart users still haven’t fully appreciated the power of extension types. This isn’t just "another way to add a method" — it’s a fundamentally different approach to working with types in Dart!&lt;/p&gt;

&lt;p&gt;This situation doesn’t sit well with me. I’m here to show you the full potential of this awesome feature by digging deeper than superficial overviews. Let’s start from the very beginning — with the introduction of extension methods — so we can trace how Dart evolved toward extension types.&lt;/p&gt;

&lt;h2&gt;
  
  
  The history of static type extensions: Extension Methods
&lt;/h2&gt;

&lt;p&gt;The release of &lt;a href="https://dart.dev/resources/language/evolution#dart-2-7" rel="noopener noreferrer"&gt;Dart 2.7&lt;/a&gt; on December 11, 2019, brought several changes, including a new feature — extension methods (hereafter, EM).&lt;/p&gt;

&lt;p&gt;In a way, this was a revolution, providing developers with a solution to one of the language’s fundamental problems: extending the functionality of closed or third-party types.&lt;/p&gt;

&lt;p&gt;Today, it’s simply impossible to imagine code without extensions. Many libraries (like &lt;code&gt;go_router&lt;/code&gt;, &lt;code&gt;bloc&lt;/code&gt;, etc.) rely on them, with a common use case being extending &lt;code&gt;BuildContext&lt;/code&gt;. And all this without modifying source code, creating wrappers, or using inheritance!&lt;/p&gt;

&lt;p&gt;But time doesn’t stand still, and the Dart team has introduced entirely new concepts to the language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extension Types
&lt;/h2&gt;

&lt;p&gt;While EM allowed adding new methods to existing types, the introduction of extension types (hereafter, ET) in &lt;a href="https://dart.dev/resources/language/evolution#dart-3-3" rel="noopener noreferrer"&gt;Dart 3.3&lt;/a&gt; took things further — enabling the creation of new static types on top of existing implementations.&lt;/p&gt;

&lt;p&gt;This is a fundamentally different level of abstraction. But as I began studying this feature more closely, I encountered an interesting paradox: despite its depth and capabilities, ET "remain in the shadows," underutilized by most developers.&lt;/p&gt;

&lt;p&gt;The situation becomes even more intriguing when you realize that the only widespread example of ET usage is package:web, one of the key packages for the Dart/Flutter ecosystem. Outside of web development, they’re almost absent in popular packages.&lt;/p&gt;

&lt;p&gt;Does this mean the feature is weak, or have we simply not yet realized its potential? Let’s figure it out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Key similarities and fundamental differences
&lt;/h2&gt;

&lt;p&gt;I remember my first encounter with ET. At first, it was completely unclear why we needed an "analog" of the already existing EM, since at a glance, they seemed very similar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both EM and ET work with existing types, adding functionality to types you can’t or don’t want to modify directly (e.g., types from &lt;code&gt;dart:core&lt;/code&gt;, classes from third-party libraries).&lt;/li&gt;
&lt;li&gt;Neither EM nor ET create additional objects in memory when called — the compiler "unwraps" them during compilation.&lt;/li&gt;
&lt;li&gt;At runtime, the value remains an instance of the original type (&lt;code&gt;int&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt;, etc.). No surprises with sudden type changes.&lt;/li&gt;
&lt;li&gt;Both mechanisms allow adding methods, getters, setters, and overriding operators that work with instances of the type.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But all of the above is just the tip of the iceberg. Despite their apparent similarity, the differences between the two mechanisms are significant, both in terms of capabilities and conceptually. Let’s examine them in more detail.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;While EM "simply" adds new methods to an instance of an existing type, ET creates a new type based on the underlying one, effectively introducing an abstraction that the compiler treats as an independent type.&lt;/li&gt;
&lt;li&gt;EM is automatically compatible with all instances of the extended class (e.g., every &lt;code&gt;int&lt;/code&gt; has methods from &lt;code&gt;extension on int&lt;/code&gt;). ET, however, requires explicit "wrapping" of the base type instance and explicit type casting.&lt;/li&gt;
&lt;li&gt;ET supports static members — you can declare static methods, fields, named constructors, and factory constructors.&lt;/li&gt;
&lt;li&gt;One of ET’s key features is member hiding, allowing control over the API of any type in Dart.&lt;/li&gt;
&lt;li&gt;ET carries not only new or overridden functionality for the base type but also semantic meaning. EM, on the other hand, is perceived more as a set of utility methods.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Extension types in action: &lt;code&gt;package:web&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;With theory out of the way, let’s look at a real-world example of ET usage. I wondered: "Why were ET used in package:web instead of regular extensions?" and found several key reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ensuring type safety&lt;/strong&gt;. Even with &lt;code&gt;extension on JSObject&lt;/code&gt;, all JS objects remain the same type—the compiler doesn’t distinguish between &lt;code&gt;Window&lt;/code&gt;, &lt;code&gt;HTMLElement&lt;/code&gt;, etc. ET creates objects of unique types, even though at runtime they’re still &lt;code&gt;JSObject&lt;/code&gt;. The Dart compiler prevents confusion between objects.&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;// Without ET: Runtime error only  &lt;/span&gt;
&lt;span class="n"&gt;Window&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getWindow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'div'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Window doesn’t have this method!  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Abstraction without overhead&lt;/strong&gt;. The eternal problem with any wrapper classes is extra memory allocations. For performance-critical systems, this is a crucial factor to avoid. ET doesn’t create additional objects at runtime while adding compile-time type safety guarantees. In &lt;code&gt;package:web&lt;/code&gt;, frequent JS-API calls (e.g., in animations or event handling) would cause GC pressure and FPS drops if full wrapper objects were created. ET enables both maximum performance and resource efficiency—the wrapper exists only at the type level, with direct JS-object access at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JS interaction semantics&lt;/strong&gt;. The &lt;code&gt;package:js&lt;/code&gt; library worked with the &lt;code&gt;dynamic&lt;/code&gt; type, which was far from ideal. This made the code unsafe, as errors could only be caught at runtime. ET changes the game: all JS objects remain statically typed, and development becomes more convenient thanks to autocompletion.&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;// Highly simplified ET declaration for JS objects:  &lt;/span&gt;
&lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="nf"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JSObject&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;JSObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;external&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="kd"&gt;external&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;alert&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;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JSObject&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;JSObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;external&lt;/span&gt; &lt;span class="n"&gt;HTMLElement&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;querySelector&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;selector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="nf"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JSObject&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;JSObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;external&lt;/span&gt; &lt;span class="n"&gt;DOMTokenList&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;classList&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;h2&gt;
  
  
  Why haven’t Extension Types "taken off"?
&lt;/h2&gt;

&lt;p&gt;The paradox of ET is that their success case has also become their curse. While demonstrating the power of the concept, &lt;code&gt;package:web&lt;/code&gt; is a niche domain for most developers, making ET seem overly complex to learn.&lt;/p&gt;

&lt;p&gt;So why don’t we see widespread ET adoption?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cognitive load&lt;/strong&gt;. The concept of a static wrapper that exists only at compile-time requires a paradigm shift. Developers are used to two typical models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating wrapper classes with inherent overhead.&lt;/li&gt;
&lt;li&gt;Using extension methods for utility functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ET, however, is a hybrid—you create a new type (e.g., UserId), but it disappears at runtime. This causes dissonance, forcing developers to constantly keep the difference between static and runtime semantics in mind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lack of design practices&lt;/strong&gt;. ET solve a narrow set of problems: optimizing wrapper classes, typed and protected interfaces. But these scenarios rarely arise in everyday app development. When they do, developers don’t associate them with ET. There’s no established understanding of how to design ET or when to choose them over classes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Synthetic examples&lt;/strong&gt;. Dart’s documentation offers examples like wrapping an ID over int. While technically correct, this doesn’t showcase ET’s full potential—and frankly, it’s uninspiring. Why use ET when typedef works?&lt;/p&gt;

&lt;p&gt;The lack of a "bridge" between &lt;code&gt;UserID(int id)&lt;/code&gt; and &lt;code&gt;package:web&lt;/code&gt;, combined with the need to consider new language features during design, has led to this genuinely cool Dart feature remaining underutilized.&lt;/p&gt;

&lt;p&gt;Constructive criticism is good, but there’s a saying: "If you criticize, suggest." I invite you to consider a more "real-world" application of this powerful Dart feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encapsulating internal logic and object-oriented interaction with procedures
&lt;/h2&gt;

&lt;p&gt;Imagine this scenario: you’re working with low-level APIs or a set of procedures. A common challenge developers face is the lack of an expressive API, which can lead to hard-to-diagnose errors.&lt;/p&gt;

&lt;p&gt;As an example, let’s take working with isolates, where procedural style dominates, requiring manual port management. This is a powerful but low-level mechanism where mistakes are easy to make. ET offers an alternative, allowing elegant object-oriented contracts on top of low-level mechanisms. Let’s see how Dart’s ET can adapt the base isolate worker implementation for specific tasks.&lt;/p&gt;

&lt;p&gt;First, let’s implement a basic worker class:&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;// Base worker implementation&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;Worker&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;SendPort&lt;/span&gt; &lt;span class="n"&gt;sP&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;ReceivePort&lt;/span&gt; &lt;span class="n"&gt;rP&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;Isolate&lt;/span&gt; &lt;span class="n"&gt;isolate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

  &lt;span class="n"&gt;Worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_&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;isolate&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;sP&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;rP&lt;/span&gt;&lt;span class="p"&gt;,&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;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="kt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SendPort&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;entryPoint&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;rp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReceivePort&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;isolate&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;Isolate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="n"&gt;entryPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendPort&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;sp&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;rp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;first&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;SendPort&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;Worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isolate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rp&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;// Handler for RemoteMessage events  &lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;_isolateEntryPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SendPort&lt;/span&gt; &lt;span class="n"&gt;sendPort&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;receivePort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReceivePort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
  &lt;span class="n"&gt;sendPort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receivePort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendPort&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

  &lt;span class="n"&gt;receivePort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&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="kd"&gt;async&lt;/span&gt; &lt;span class="p"&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;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;RemoteMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&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;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;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;computation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendPort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
            &lt;span class="k"&gt;break&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;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendPort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&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="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;UnimplementedError&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;// Handler for String events  &lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;_isolateEntryPoint2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SendPort&lt;/span&gt; &lt;span class="n"&gt;sendPort&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;receivePort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReceivePort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
  &lt;span class="n"&gt;sendPort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receivePort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendPort&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

  &lt;span class="n"&gt;receivePort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&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="kd"&gt;async&lt;/span&gt; &lt;span class="p"&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;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  
          &lt;span class="n"&gt;print&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="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;UnimplementedError&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;Notice that all workers created via the static create method have different entry points (&lt;code&gt;_isolateEntryPoint&lt;/code&gt; and &lt;code&gt;_isolateEntryPoint2&lt;/code&gt;). Yet, the &lt;code&gt;Worker&lt;/code&gt; class itself lacks additional methods for interaction. Let’s fix this with ET.&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;// Only for printing messages  &lt;/span&gt;
&lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="nf"&gt;PrintWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Worker&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;)&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;print&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;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Isolate &lt;/span&gt;&lt;span class="si"&gt;${Isolate.current.hashCode}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;$message&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="c1"&gt;// Only for heavy computations  &lt;/span&gt;
&lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="nf"&gt;ComputeWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Worker&lt;/span&gt; &lt;span class="n"&gt;worker&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="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;compute&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="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;computation&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;responsePort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReceivePort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RemoteMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="n"&gt;computation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="n"&gt;responsePort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendPort&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;await&lt;/span&gt; &lt;span class="n"&gt;responsePort&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="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 idea is that the base &lt;code&gt;Worker&lt;/code&gt; class hides the general isolate creation logic, while ET adds specialized methods that restrict interaction with the worker.&lt;/p&gt;

&lt;p&gt;What does this give us in practice? If you create different workers for different tasks (e.g., one for logging and three for heavy computations) or even manage their lifecycle dynamically, this approach protects against errors when working with the worker’s public API, which is implemented using ET. The entire interaction API is centralized, not scattered across the code, and doesn’t require inheritance hierarchies. Additionally, the code becomes more expressive: &lt;code&gt;compare worker.sendPort.send(() =&amp;gt; 2 + 2)&lt;/code&gt; to &lt;code&gt;computeWorker.compute(() =&amp;gt; 2 + 2)&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="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="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;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PrintWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isolateEntryPoint2&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;w2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ComputeWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Worker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isolateEntryPoint&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  

  &lt;span class="c1"&gt;// Only use methods declared in the extension  &lt;/span&gt;
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello from isolate"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt; &lt;span class="c1"&gt;// Error!  &lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&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;w2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compute&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="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="p"&gt;);&lt;/span&gt;  

  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Computation res: &lt;/span&gt;&lt;span class="si"&gt;$result&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We’ve encapsulated all low-level control code, message formatting, and port-sending logic. Users only see semantically meaningful operations.&lt;/li&gt;
&lt;li&gt;New worker types can be added without modifying the base Worker class.&lt;/li&gt;
&lt;li&gt;Unlike implementing this via wrapper classes or inheritance, there’s no overhead (ET don’t create additional objects in memory). At runtime, PrintWorker disappears, leaving only the original Worker—but with compile-time safety guarantees.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Extension types aren’t just another Dart feature — they’re a fundamentally new way to design abstractions. They offer something unattainable with either extension methods or classes: static type safety without runtime overhead.&lt;/p&gt;

&lt;p&gt;Mastering extension types is another step toward professional Dart proficiency. Their use requires creativity, practice, and exposure, but once you find your first real-world use case, you’ll unlock a new level of expressiveness and control in your code.&lt;/p&gt;

</description>
      <category>dart</category>
      <category>flutter</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
