<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Guru Meditation]]></title><description><![CDATA[random experiences of a mobile developer]]></description><link>https://blog.burkharts.net</link><generator>RSS for Node</generator><lastBuildDate>Sun, 17 May 2026 14:54:36 GMT</lastBuildDate><atom:link href="https://blog.burkharts.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Marketing B2C Apps on Social Media]]></title><description><![CDATA[You can build a great product and still have nobody use it. I know because I've done it twice, and I'm about to do it a third time with Latina UGC.
Two Apps, Same Problem
My first app, Sharing Me, is ]]></description><link>https://blog.burkharts.net/marketing-b2c-apps-on-social-media</link><guid isPermaLink="true">https://blog.burkharts.net/marketing-b2c-apps-on-social-media</guid><dc:creator><![CDATA[Thomas Burkhart]]></dc:creator><pubDate>Fri, 10 Apr 2026 10:01:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6683273388aa4012c9a05d85/83764723-33a9-478d-8c82-a603916af1e3.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You can build a great product and still have nobody use it. I know because I've done it twice, and I'm about to do it a third time with <a href="https://latinaugc.com">Latina UGC</a>.</p>
<h2>Two Apps, Same Problem</h2>
<p>My first app, <a href="https://www.sharingme.app">Sharing Me</a>, is a mindful daily journaling app. One thought per day, share it with your partner or family, look back and see what you wrote on this day last month or last year. It's simple, it's beautiful, it works. My second, <a href="https://www.dontspiral.app">Don't Spiral</a>, is an AI-powered safe space for people dealing with relationship anxiety. Talk it out at 3am before you send that text you'll regret. Both are real products solving real problems for real people.</p>
<p>And both face the exact same marketing challenge: how do you get someone to stop scrolling long enough to care?</p>
<h2>The Scroll Problem</h2>
<p>Social media was supposed to be the great equalizer for indie developers. No need for billboards or TV spots. Just post your thing and let the algorithm do its magic. Except the algorithm doesn't care about your thing. It cares about keeping people on the platform.</p>
<p>That means your carefully crafted app screenshot is competing against someone's vacation photos, a funny dog video, rage bait about politics, and a thirst trap. You have maybe 0.3 seconds before a thumb flicks you into oblivion.</p>
<p>This is the core tension of B2C app marketing in 2026: the platforms you need to reach your users are actively optimized against you reaching them. Unless you pay. And even when you pay, you're still fighting for attention against content that is inherently more engaging than "hey, check out my journaling app."</p>
<h2>What Actually Stops the Scroll</h2>
<p>After months of trying different approaches, I've learned a few things about what makes someone pause.</p>
<p>Emotion beats features. Nobody stops scrolling for "end-to-end encryption and custom color themes." They stop for "I wrote one sentence every day for a year and cried reading it back." The product is not the hook. The feeling is the hook.</p>
<p>Faces work. This sounds obvious but it took me too long to internalize. A real human face expressing a real emotion outperforms any app screenshot, any animation, any clever graphic. People are wired to look at other people.</p>
<p>Conflict and tension hold attention. "I almost ruined my relationship last night" keeps someone reading. "Relationship anxiety support app" does not.</p>
<p>The first frame is everything. In video content, if your opening frame doesn't create a question or an emotion, the rest of the video doesn't exist. It will never be seen.</p>
<h2>Why I'm Building Latina UGC</h2>
<p>This is actually why I started building <a href="https://latinaugc.com">Latina UGC</a>. I experienced the problem firsthand. I needed authentic, scroll-stopping video content for my apps, and I had two options: spend hours learning to create it myself, or pay a fortune to agencies that didn't understand my product.</p>
<p>But there's a more personal reason too. Last year I moved from Germany to Colombia, and it changed how I see a lot of things. One of the biggest surprises was realizing how painfully ignorant most of Europe is about Latin America. The depth of culture, the warmth, the creativity here barely registers in the European consciousness. And once you live here, it's impossible to miss how naturally expressive and emotional people are. A Colombian reacting to something on camera has a genuineness and energy that you just can't fake or train into someone. It seemed like the most logical thing in the world to focus this platform on Latin American creators.</p>
<p>Latina UGC is a marketplace connecting brands with Latin and Hispanic creators for authentic UGC video reactions. Real people, real emotions, fast turnaround. The kind of content that actually stops the scroll because it looks like something a friend posted, not an ad.</p>
<p>The irony is not lost on me that I now need to market a marketing platform on the same social media channels that made me realize I needed it in the first place.</p>
<h2>What I've Learned So Far</h2>
<p>Building three B2C products has taught me that the product is maybe 30% of the work. The other 70% is getting it in front of people who need it, in a format that survives the scroll. Most indie developers, myself included, dramatically underestimate this ratio.</p>
<p>If you're building a consumer app right now, my honest advice: start thinking about distribution before you write your first line of code. Figure out what your "scroll stopper" is. If you can't articulate it in one sentence, you have a marketing problem that no amount of feature development will fix.</p>
<p>The product has to be good. But good isn't enough. It never was, and social media has only made that more true.</p>
<p>You can find my apps at <a href="https://www.sharingme.app">sharingme.app</a> and <a href="https://www.dontspiral.app">dontspiral.app</a>, and the marketplace I'm building at <a href="https://latinaugc.com">latinaugc.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[The Unsung Heroes of OSS]]></title><description><![CDATA[The Flutter ecosystem is vast, and if you've built anything non-trivial with it, you've almost certainly depended on packages maintained by people you've never heard of. No keynotes, no massive Twitte]]></description><link>https://blog.burkharts.net/the-unsung-heroes-of-oss</link><guid isPermaLink="true">https://blog.burkharts.net/the-unsung-heroes-of-oss</guid><category><![CDATA[OSS]]></category><dc:creator><![CDATA[Thomas Burkhart]]></dc:creator><pubDate>Fri, 10 Apr 2026 09:52:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6683273388aa4012c9a05d85/fc4fe1e4-b314-407b-825e-90fbdf0c0cec.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The Flutter ecosystem is vast, and if you've built anything non-trivial with it, you've almost certainly depended on packages maintained by people you've never heard of. No keynotes, no massive Twitter followings, just years of quietly solving hard problems so the rest of us don't have to. I want to shine a light on three developers whose open-source work has made my life (and probably yours) significantly easier.</p>
<h2>Christian Findlay: The Cross-Platform Veteran</h2>
<p>Christian is one of those developers who's been shipping open-source libraries since long before it was fashionable. Based in Melbourne, Australia, he runs <a href="https://nimblesite.co">Nimblesite</a>, a software development agency specializing in Flutter, .NET, and cloud architecture. With over 20 years of professional experience, he bridges two ecosystems that don't always talk to each other.</p>
<p>His GitHub profile reads like a catalog of problems that needed solving. <a href="https://github.com/MelbourneDeveloper/Device.Net">Device.Net</a> (667 stars) is a cross-platform C# framework for connected devices covering USB, HID, and serial ports, filling a gap that Microsoft themselves never closed. <a href="https://github.com/MelbourneDeveloper/RestClient.Net">RestClient.Net</a> (365 stars) makes REST calls straightforward across any .NET platform. On the Dart side, his <a href="https://github.com/MelbourneDeveloper/ioc_container">ioc_container</a> brings lightweight, high-performance dependency injection to Flutter.</p>
<p>What I appreciate most about Christian is that he doesn't just write code, he writes about it. His blog at <a href="https://www.christianfindlay.com/">christianfindlay.com</a> is a goldmine of practical articles on Flutter testing, .NET architecture, and AI-assisted development. If you're interested in Flutter widget testing, his deep dive on <a href="https://www.nimblesite.co/blog/flutter_full_app_widget_testing/">full app widget testing</a> on the Nimblesite blog is essential reading. He makes a compelling case for testing the entire widget tree rather than isolating individual components, an approach that yields higher coverage with less code.</p>
<h2>Milad Akarie: The Routing and DI Architect</h2>
<p>If you've ever used type-safe routing in Flutter, there's a good chance Milad Akarie built the tool you're relying on. Based in Ankara, Turkey, Milad is the creator of some of the most widely adopted infrastructure packages in the Flutter ecosystem, all published under <a href="https://www.codeness.ly">codeness.ly</a>.</p>
<p>The numbers speak for themselves. <a href="https://github.com/Milad-Akarie/auto_route_library">auto_route</a> (1.7k stars) is a code-generated routing solution that handles strongly-typed arguments, deep linking, and tab navigation with minimal boilerplate. <a href="https://github.com/Milad-Akarie/injectable">injectable</a> (615 stars) is built on top of my package <a href="https://pub.dev/packages/get_it">get_it</a> and adds a code generation layer that turns dependency injection setup from a manual chore into an annotation-driven process. It's a great example of how open-source developers build on each other's work to create something greater than the sum of its parts. <a href="https://github.com/Milad-Akarie/smooth_page_indicator">smooth_page_indicator</a> (1.4k stars) provides beautifully animated page indicators, one of those small details that makes an app feel polished. And <a href="https://github.com/Milad-Akarie/skeletonizer">skeletonizer</a> (546 stars) generates skeleton loading screens from your actual widgets, which is one of those "why didn't this exist sooner" ideas.</p>
<p>Milad is also an indie app developer. His pattern matching game <a href="https://play.google.com/store/apps/details?id=ly.codeness.turnix">Turnix</a> and the shopping list app <a href="https://apps.apple.com/us/app/shopping-list-listize/id6670314329">Listize</a> are both built with Flutter. You can find him on <a href="https://x.com/milad_akarie">X/Twitter</a> and <a href="https://milad-akarie.medium.com/">Medium</a> where he writes about Flutter development and code automation.</p>
<h2>Tim Lehmann: The UX Engineer's Engineer</h2>
<p>Tim Lehmann might be the most underrated Flutter contributor I know. He's a senior engineer with a Master's in Human-Computer Interaction, currently working on a project for Volkswagen Future Center Europe in Potsdam. He publishes his packages under <a href="https://whynotmake.it/">whynotmake.it</a> and his GitHub org <a href="https://github.com/whynotmake-it">whynotmake-it</a>, and the quality bar is remarkably high.</p>
<p>His most recent claim to fame is <a href="https://pub.dev/packages/liquid_glass_renderer">liquid_glass_renderer</a> (800+ likes), which brings Apple's liquid glass effect to Flutter using custom shaders. It's experimental, performance-sensitive work that pushes the boundaries of what Flutter can render. The <a href="https://pub.dev/packages/motor">motor</a> package (194 likes) provides a unified motion system for physics-based springs and duration-based curves, essentially the animation primitives that Material Expressive Design calls for, delivered before the Flutter SDK caught up. <a href="https://pub.dev/packages/heroine">heroine</a> (286 likes) reimagines hero transitions with spring physics, and <a href="https://pub.dev/packages/scribble">scribble</a> (218 likes) is a freehand drawing library with pressure sensitivity and variable line width.</p>
<p>Tim also builds developer tooling. <a href="https://github.com/whynotmake-it/figmage">figmage</a> generates Flutter themes directly from Figma files, and his <a href="https://github.com/whynotmake-it/dart-coverage-assistant">Dart Coverage Assistant</a> GitHub Action generates coverage reports and badges for your CI pipeline. His blog post on <a href="https://whynotmake.it/flutter-ci-tour/">setting up Flutter CI on GitHub</a> is one of the most practical guides I've seen on the topic.</p>
<h2>Why This Matters</h2>
<p>These three developers represent something important about the open-source ecosystem: the people who build the infrastructure rarely get the recognition they deserve. Their packages have millions of combined downloads. They save thousands of developers countless hours every week. And most of the time, they do it for free, alongside their day jobs and freelance work.</p>
<p>If you use any of their packages, consider starring their repos, sponsoring their work, or simply saying thanks. Open source runs on goodwill, and a little recognition goes a long way.</p>
]]></content:encoded></item><item><title><![CDATA[Practical Flutter Architecture]]></title><description><![CDATA[Okay, I'm just teasing. There are many ways to structure an app and choose state management. Some methods are not great, some are okay, and some have clear benefits. As always, this is my opinion based on many years in this field.
Some theorie
State ...]]></description><link>https://blog.burkharts.net/practical-flutter-architecture</link><guid isPermaLink="true">https://blog.burkharts.net/practical-flutter-architecture</guid><category><![CDATA[Flutter]]></category><category><![CDATA[architecture]]></category><category><![CDATA[get_it]]></category><category><![CDATA[watch_it]]></category><category><![CDATA[State Management ]]></category><dc:creator><![CDATA[Thomas Burkhart]]></dc:creator><pubDate>Wed, 26 Feb 2025 17:33:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740342935346/0d8204c9-e2dc-4810-a243-ed955555ce0f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Okay, I'm just teasing. There are many ways to structure an app and choose state management. Some methods are not great, some are okay, and some have clear benefits. As always, this is my opinion based on many years in this field.</p>
<h2 id="heading-some-theorie">Some theorie</h2>
<h3 id="heading-state-management">State Management</h3>
<p>Before I started using Flutter, I had never heard the term "state management," and I still think it's often mentioned without a clear explanation. To me, state management is about making sure our UI updates whenever the data changes and figuring out how to update data from the UI. That's all. Often, people confuse it with how we access our data from the UI layer, but that's not really state management.</p>
<h3 id="heading-layered-architecture">Layered Architecture</h3>
<p>Over the years, we discovered that dividing your app into different layers has some advantages. The most common structure you'll find is a three-layered architecture, organized from top to bottom as follows:</p>
<ul>
<li><p>UI</p>
</li>
<li><p>Business logic (all logic that transforms or controls your data)</p>
</li>
<li><p>Services (connects to the outside world, OS, or network)</p>
</li>
</ul>
<p>The reasons for splitting them like this are:</p>
<ul>
<li><p>Separating the UI from the business logic allows for easy automated testing of your business logic.</p>
</li>
<li><p>Splitting the code below the UI lets you replace the real service with mocked versions, so you can test your app's logic without relying on a working backend or even test the business layer on another platform that doesn’t offer the needed APIs.</p>
</li>
<li><p>Another reason for such layers is that in big projects, some teams choose to divide the team along these layers, which can make sense if people are more experienced in certain layers or enjoy them more.</p>
</li>
</ul>
<p>You will encounter other proposals with many more layers or not even horizontal layers. These can make sense for some IT systems, but we rarely need them for mobile apps.</p>
<blockquote>
<p><strong>Clean Architecture</strong><br />I’m still frustrated with “Uncle Bob” for using the term ‘clean’ for both his book ‘Clean Code’ and especially for ‘Clean Architecture,’ because it makes it seem like the only way to write software. He does provide some good advice in his book (though there are different viewpoints), but ‘Clean Architecture’ is an excessive example of overengineering and overabstraction. For mobile apps, it's total overkill.</p>
</blockquote>
<h3 id="heading-mvvm-mvc-mvu-etc">MVVM, MVC, MVU etc…</h3>
<p>You will come across these acronyms that describe how the UI and business logic are connected. They were mainly created to simplify working with certain frameworks, but they can be problematic if used with a different framework where they don't fit. They also don't provide guidance on how to structure the entire app. They aren't true architectures, which makes them even more confusing.</p>
<h4 id="heading-mvvm">MVVM</h4>
<p>MVVM is often used and recommended for mobile apps. Here are the layers:</p>
<ul>
<li><p><strong>M</strong>odel - contains all business logic (possibly including a service layer)</p>
</li>
<li><p><strong>V</strong>iew - the UI part</p>
</li>
<li><p><strong>V</strong>iew<strong>M</strong>odel - acts as a glue layer between the UI and your business logic.</p>
</li>
</ul>
<p>Why ViewModels? They became popular with XAML-based UI frameworks like Microsoft's WPF or Xamarin Forms, as well as past native Android development. In all of these, you define the UI not in code but declaratively in some XML or JSON-based markup language. To connect the UI that these frameworks produced from the markup, you needed to define a ViewModel for every page/widget, which provided the data to display and functions to bind to buttons in the UI. The actual connection of these parts happened automatically via naming conventions, known as automatic binding.</p>
<p>Why explain this in such detail? Because we don’t have automatic bindings in Flutter.</p>
<h4 id="heading-mvc">MVC</h4>
<ul>
<li><p><strong>M</strong>odel</p>
</li>
<li><p><strong>V</strong>iew</p>
</li>
<li><p><strong>C</strong>ontroller</p>
</li>
</ul>
<p>There are several variations, but the original definition was that all data always passes through the controller as it moves from the UI to the Model and back. Today, there doesn't seem to be a clear understanding of what it truly means, which makes this pattern almost useless for describing anything.</p>
<h4 id="heading-mvu">MVU</h4>
<p>If any of these patterns come close, it is <strong>MVU</strong>, which stands for <strong>M</strong>odel - <strong>V</strong>iew - <strong>U</strong>pdate. It first appeared with the <a target="_blank" href="https://elmprogramming.com/">Elm</a> programming language. It treats your app as a mathematical function where <code>View = appLogic(Model)</code>. This means every time your Model/Data changes, the UI is recreated based on the new data. This approach somewhat aligns with Flutter, although Flutter has multiple ways to ensure that only the necessary parts of the UI are rebuilt when the data changes.</p>
<h4 id="heading-where-does-this-leave-us">Where does this leave us?</h4>
<p>In my opinion, all these patterns that are often referenced do not really help in describing what we need to write a Flutter application. So maybe we should discard them and try to come up with an architecture that truly fits Flutter?</p>
<h3 id="heading-pragmatic-flutter-architecture-pfa">Pragmatic Flutter Architecture (PFA)</h3>
<blockquote>
<p>In the past, I used the term RVMS (<strong>R</strong>eactive <strong>V</strong>iew <strong>M</strong>anager <strong>S</strong>ervices), but after several years of working with it, I realized that you need more than just the first letters of the architecture's components. If you have a better idea for what I describe here, please let me know.</p>
</blockquote>
<p>I chose <em>Pragmatic</em> because what I suggest might break some rules often cited as the “correct” way to write code. Some people might enjoy academic debates about topics like ‘Are service locators an anti-pattern?’ but ultimately, we need to deliver apps, so I prefer to focus on that. See my <a target="_blank" href="https://blog.burkharts.net/understanding-the-problems-with-dogmatic-programming-advice">previous post</a>.</p>
<h3 id="heading-core-goals">Core goals</h3>
<ol>
<li><p>Easy to explore and understand</p>
</li>
<li><p>No configuration by convention (violates the first goal)</p>
</li>
<li><p>Avoiding complex code that's hard to understand (violates the first goal)</p>
</li>
<li><p>Flutter code should still look like Flutter code</p>
</li>
<li><p>Clear separation of concerns</p>
</li>
<li><p>Easy to test</p>
</li>
<li><p>Able to scale</p>
</li>
<li><p>Minimal boring boilerplate code</p>
</li>
<li><p>Flexible, not too rigid</p>
</li>
<li><p>Easy to get started with</p>
</li>
<li><p>Fun to work with</p>
</li>
</ol>
<h3 id="heading-structure-of-a-pfa-app">Structure of a PFA App</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731854958909/42c1be7b-5da6-47ea-81a6-a77805470df2.png" alt class="image--center mx-auto" /></p>
<p>.</p>
<h3 id="heading-services">Services</h3>
<ul>
<li><h4 id="heading-encapsulate-functionality-beyond-the-apps-boundaries-such-as">Encapsulate functionality beyond the app's boundaries, such as:</h4>
<p>  - REST APIs<br />  - Databases<br />  - OS services like Contacts/Calendar<br />  - Hardware features like location and acceleration sensors</p>
</li>
<li><p>Convert data from/to the external data format (e.g., JSON) to domain objects</p>
</li>
<li><p>Do not change any app state on their own</p>
</li>
<li><p>Each service should only wrap one external aspect.</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-comment">/// <span class="markdown">Example for a service from the refactored Compass app</span></span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SharedPreferencesService</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> _tokenKey = <span class="hljs-string">'TOKEN'</span>;
  <span class="hljs-keyword">final</span> _log = Logger(<span class="hljs-string">'SharedPreferencesService'</span>);

  Future&lt;<span class="hljs-built_in">String?</span>&gt; fetchToken() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">final</span> sharedPreferences = <span class="hljs-keyword">await</span> SharedPreferences.getInstance();
      _log.finer(<span class="hljs-string">'Got token from SharedPreferences'</span>);
      <span class="hljs-keyword">return</span> sharedPreferences.getString(_tokenKey);
    } <span class="hljs-keyword">on</span> Exception {
      <span class="hljs-keyword">throw</span> Exception(<span class="hljs-string">'Failed to get auth token'</span>);
    }
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; saveToken(<span class="hljs-built_in">String?</span> token) <span class="hljs-keyword">async</span> {
    ....
  }
}
</code></pre>
<blockquote>
<p>all code samples here are just to ilustrate the concepts, they are not necesary to understand the concepts</p>
</blockquote>
<h3 id="heading-managers">Managers</h3>
<ul>
<li><p>Wrap <strong>semantically related</strong> business logic, such as:</p>
<ul>
<li><p>User Management/Authentication</p>
</li>
<li><p>Notification Management</p>
</li>
<li><p>Invoices</p>
</li>
</ul>
</li>
<li><p>Do not directly map to a specific View (Managers ≠ ViewModel)</p>
</li>
<li><p>Often provide CRUD functionality for domain objects</p>
</li>
<li><p>Implement any business logic that changes the app state</p>
</li>
<li><p>Use Services or other Managers</p>
</li>
<li><p>Provide Functions/Commands/ValueListenables for the UI</p>
</li>
<li><p>Accessed through ServiceLocator or DI</p>
</li>
<li><p>Sometimes, it makes sense to move logic from a Manager into a Proxy object, especially when dealing with Lists of Proxies connected to Item-Widgets in a ListView.</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-comment">/// <span class="markdown">Part of a Manager of the refactored compass app.</span></span>
<span class="hljs-comment">/// <span class="markdown">if you wonder why there is no error handling, that is done by the commands internally</span></span>
<span class="hljs-comment">/// <span class="markdown">any exception thrown inside a command will be handled depending you your settings.</span></span>
<span class="hljs-comment">/// <span class="markdown">Because here we have to error routing defined they will be reported at the global </span></span>
<span class="hljs-comment">/// <span class="markdown">command error handler</span></span>
<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BookingManager</span> </span>{
  <span class="hljs-comment">/// <span class="markdown">combines the busy state of the booking commands</span></span>
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> ValueListenable&lt;<span class="hljs-built_in">bool</span>&gt; isBusy =
      createBookingCommand.isExecuting.mergeWith(
    [loadBookingCommand.isExecuting, deleteBookingCommand.isExecuting],
  );

  <span class="hljs-keyword">final</span> _log = Logger(<span class="hljs-string">'BookingViewModel'</span>);

  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> Command&lt;<span class="hljs-keyword">void</span>, <span class="hljs-keyword">void</span>&gt; createBookingCommand =
      Command.createAsyncNoParamNoResult(
    () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> itineraryConfig =
          <span class="hljs-keyword">await</span> di&lt;ItineraryConfigManager&gt;().getItineraryConfig();
      _booking.value = <span class="hljs-keyword">await</span> createFrom(itineraryConfig);
    },
    debugName: cmdCreateBooking,
  );

  <span class="hljs-comment">/// <span class="markdown">Loads booking by id</span></span>
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> Command&lt;<span class="hljs-built_in">int</span>, <span class="hljs-keyword">void</span>&gt; loadBookingCommand =
      Command.createAsyncNoResult(
    (id) <span class="hljs-keyword">async</span> {
      _booking.value = <span class="hljs-keyword">await</span> getBooking(id);
    },
    debugName: cmdLoadBooking,
  );
</code></pre>
<h3 id="heading-views">Views</h3>
<ul>
<li><p>Full Page or high-level Widget that fully implements a specific feature</p>
</li>
<li><p><strong>Are self-responsible,</strong> meaning they know what data they need and can operate independently of a specific location in the widget tree</p>
</li>
<li><p>Select the data they need from <strong>Managers</strong> or directly from <strong>Services</strong> (read-only), typically by listening to ValueListenables or Streams</p>
</li>
<li><p>Modify data by using functions or <strong>Commands</strong> in <strong>Managers</strong></p>
</li>
<li><p><strong>Don’t modify</strong> <strong>data/state</strong> by using Services</p>
</li>
<li><p><strong>Don’t change any state</strong> that isn’t related to the task the View is designed for; that is the responsibility of the business logic inside Managers</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-comment">/// <span class="markdown">Example from the refactored Compass app showing how watch<span class="hljs-emphasis">_it is used to </span></span></span>
<span class="hljs-comment">/// <span class="markdown"><span class="hljs-emphasis">obeserve the state of a Manager and its commands</span></span></span>
<span class="hljs-comment">/// <span class="markdown"><span class="hljs-emphasis">Commands itself are ValueListenables and offer additional state properties as </span></span></span>
<span class="hljs-comment">/// <span class="markdown"><span class="hljs-emphasis">ValueListenables like errors or busy states</span></span></span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BookingBody</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">WatchingWidget</span> </span>{
  <span class="hljs-keyword">const</span> BookingBody({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> isBusy =
        watchValue((BookingManager bookingManager) =&gt; bookingManager.isBusy);
    <span class="hljs-keyword">final</span> creationError = watchValue((BookingManager bookingManager) =&gt;
        bookingManager.createBookingCommand.errors);
    <span class="hljs-keyword">final</span> loadError = watchValue((BookingManager bookingManager) =&gt;
        bookingManager.loadBookingCommand.errors);
    <span class="hljs-keyword">if</span> (isBusy) {
      <span class="hljs-keyword">const</span> Center(
        child: CircularProgressIndicator(),
      );
    }
    <span class="hljs-keyword">final</span> booking =
        watchValue((BookingManager bookingManager) =&gt; bookingManager.booking);

    <span class="hljs-comment">// If fails to create booking, tap to try again</span>
    <span class="hljs-keyword">if</span> (creationError != <span class="hljs-keyword">null</span>) {
      <span class="hljs-keyword">return</span> Center(
        child: ErrorIndicator(
          title: AppLocalization.of(context).errorWhileLoadingBooking,
          label: AppLocalization.of(context).tryAgain,
          onPressed: di&lt;BookingManager&gt;().createBookingCommand.execute,
        ),
      );
    }
    <span class="hljs-comment">// If existing booking fails to load, tap to go /home</span>
    <span class="hljs-keyword">if</span> (loadError != <span class="hljs-keyword">null</span>) {
      <span class="hljs-keyword">return</span> Center(
        child: ErrorIndicator(
          title: AppLocalization.of(context).errorWhileLoadingBooking,
          label: AppLocalization.of(context).close,
          onPressed: () =&gt; context.go(Routes.home),
        ),
      );
    }
    <span class="hljs-keyword">if</span> (booking == <span class="hljs-keyword">null</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> SizedBox();
    <span class="hljs-keyword">return</span> CustomScrollView(
      slivers: [
        SliverToBoxAdapter(child: BookingHeader(booking: booking)),
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              <span class="hljs-keyword">final</span> activity = booking.activity[index];
              <span class="hljs-keyword">return</span> _Activity(activity: activity);
            },
            childCount: booking.activity.length,
          ),
        ),
        <span class="hljs-keyword">const</span> SliverToBoxAdapter(child: SizedBox(height: <span class="hljs-number">200</span>)),
      ],
    );
  }
}
...
</code></pre>
<h3 id="heading-data-objects">Data Objects</h3>
<h4 id="heading-domainbusiness-objects">Domain/Business Objects</h4>
<ul>
<li><p>These objects represent real-world items or abstract concepts from the application domain, such as users, invoices, and tickets.</p>
</li>
<li><p>If they have methods, they ONLY change their own internal state and never affect other objects.</p>
</li>
<li><p>They often provide factory functions like to/from JSON that Services can use.</p>
</li>
<li><p>They can be Proxy Objects that wrap a DTO object, as explained in my <a target="_blank" href="https://blog.burkharts.net/understanding-the-problems-with-dogmatic-programming-advice#heading-using-proxy-objects-to-get-the-best-of-both-worlds">previous post</a>. Proxy objects can also handle necessary data transformation if your DTOs contain data in a form that can’t be directly displayed in a Widget, for instance.</p>
</li>
</ul>
<blockquote>
<h4 id="heading-data-transfer-objects-dtos">Data Transfer Objects (DTOs)</h4>
<p>Depending on your backend and how you generate the code that interacts with your server API, you might need a separate version of your Domain Objects that only contains immutable data plus fromJson/toJson methods to send them over the network. Especially if they are generated from an API specification, you might not be able to add any logic on your own. Wrapping them inside a proxy object can make your life much easier.</p>
</blockquote>
<h4 id="heading-wrapper-classes">Wrapper Classes</h4>
<ul>
<li><p>Wrap multiple Domain Objects so they can be passed over a ValueListenable as a single entity, used in an item-builder in a ListView, or to make it easier to observe changes in the wrapped objects. These can include Lists, Tuples, or custom Objects.</p>
</li>
<li><p>An example could be a <code>UserSettings</code> class that doesn't exist on your Backend object but combines an <code>Account</code>, <code>Profile</code>, and <code>Settings</code> object. Often, I would create a <code>UserSettingsManager</code> object for this, as it allows all the necessary logic and permission handling to be in one place.</p>
</li>
<li><p>These can also be implemented as Proxy Objects that reference multiple DTOs.</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-comment">/// <span class="markdown">Example for a Proxy object wrapping a DTO object supporting UI updates every time </span></span>
<span class="hljs-comment">/// <span class="markdown">the target gets updated</span></span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserProxy</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ChangeNotifier</span> </span>{
  UserProxy(
    UserApiModel target,
  ) : _target = target;

  UserApiModel _target;

  UserApiModel <span class="hljs-keyword">get</span> target =&gt; _target;

  <span class="hljs-comment">/// <span class="markdown">if we ever want to update the user from the backend</span></span>
  <span class="hljs-keyword">set</span> target(UserApiModel target) {
    _target = target;
    notifyListeners();
  }

  <span class="hljs-comment">/// <span class="markdown">The user's name.</span></span>
  <span class="hljs-built_in">String</span> <span class="hljs-keyword">get</span> name =&gt; target.name;

  <span class="hljs-comment">/// <span class="markdown">The user's picture URL.</span></span>
  <span class="hljs-built_in">String</span> <span class="hljs-keyword">get</span> picture =&gt; target.picture;
}
</code></pre>
<h3 id="heading-interactions-inside-a-pfa-app">Interactions inside a PFA App</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731855729033/f14d8d0e-792d-4549-b300-ef697c39c064.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>Despite that fact that I think that using <code>Commands</code> will be an advantage, you can replace every mentioned Command with a function plus some <code>ValueListenables</code> compare the two branches of my proy demo project <a target="_blank" href="https://github.com/escamoteur/proxy_pattern_demo/tree/reference_counting_proxy">without Commands</a> vs <a target="_blank" href="https://github.com/escamoteur/proxy_pattern_demo/tree/using_flutter_commands">version with commands</a></p>
</blockquote>
<h3 id="heading-do-i-need-to-use-a-specific-package-for-pfa">Do I need to use a specific package for PFA?</h3>
<p>To create an app with the structure described above, you don't necessarily need any packages besides Flutter, but using certain packages can make your life much easier. I prefer using my own packages, which were developed with this type of architecture in mind.</p>
<p>Let's explore which components we need and where using a package might be beneficial:</p>
<h4 id="heading-making-managers-and-services-accessible">Making Managers and Services Accessible</h4>
<p>This means making them accessible from the UI, as well as from other Managers or Proxies.</p>
<ul>
<li><p><strong>Singletons in global variables</strong>: I know you might be skeptical, and it's generally against recommended best practices, but for a very simple app, this can be sufficient.</p>
</li>
<li><p><strong>Service locators</strong>: These offer more flexibility to switch out implementations and provide other useful features. In my examples, we will use <a target="_blank" href="https://pub.dev/packages/get_it">get_it</a>, which is one of the oldest packages on pub and very easy to use. However, there are others that offer similar functionality. <code>provider</code> and <code>riverpod</code> both have locator functionality as well.</p>
</li>
<li><p><strong>InheritedWidget</strong>: This works if you only need to access objects within the Widget tree. If you want to access a service from a place without a <code>BuildContext</code>, you'll need to do some extra work.</p>
</li>
</ul>
<h4 id="heading-making-the-ui-refresh-when-your-data-changes">Making the UI Refresh When Your Data Changes</h4>
<ul>
<li><p><strong>Make Your Data Observable:</strong> Let your Proxies and Domain Objects extend ChangeNotifier. Use ValueNotifiers or Streams as properties in Managers and Proxies.</p>
</li>
<li><p><strong>Make Your Widgets Observe Your Data:</strong> Use <code>ValueListenable-</code>, <code>Listenable-</code>, or <code>StreamBuilders</code> to rebuild your Widget as soon as your data changes. Or make your life easier by using <a target="_blank" href="https://pub.dev/packages/watch_it">watch_it</a> to simply watch any type of <code>Listenable</code> or <code>Stream</code>. Every state management package offers its own way to achieve the same result.</p>
</li>
</ul>
<h4 id="heading-packages-that-might-be-helpful">Packages That Might Be Helpful</h4>
<ul>
<li><p><a target="_blank" href="https://pub.dev/packages/rxdart"><strong>rxdart</strong></a><strong>:</strong> If you enjoy using <code>Streams</code>, this package allows you to transform and combine your data within your managers, limited only by your creativity.</p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/functional_listener">function_listener</a>: Provides similar functions for <code>ValueListenable</code>, along with a <code>listen()</code> method and a <code>CustomValueNotifier</code>.</p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/flutter_command">flutter_command</a>: This package wraps a function and provides several <code>ValueListenables</code> to monitor the execution of that function, along with advanced error handling. You can find a <a target="_blank" href="https://blog.burkharts.net/keeping-widgets-in-sync-with-your-data#heading-adding-fluttercommands-and-functionallistener">short introduction here</a>.</p>
</li>
</ul>
<h3 id="heading-recommended-project-structure">Recommended project structure</h3>
<p>Due to the separation into layers, many new developers try to organize their files based on which layer they belong to. So, they create one folder for services, one for managers, and one for widgets. This might seem like a reasonable approach, but as your app grows, you'll spend a lot of time searching for the widget or manager you need.</p>
<p>Organize your files by features:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731866146920/463d5657-c932-4fbf-979f-e14d6739e082.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Main folders are organized by features or larger function blocks.</p>
</li>
<li><p>A feature folder contains all the pages, widgets, managers, models, and services needed for that feature.</p>
</li>
<li><p><strong>Widgets</strong> include all widgets used on more than one page.</p>
</li>
<li><p><strong>Shared</strong> contains services, utilities, and other classes used by multiple features.</p>
</li>
<li><p><code>locator.dart</code> contains the global instance of the ServiceLocator and all object registrations.</p>
</li>
<li><p>Use <code>_</code> to sort folders you need most to the top in your IDE.</p>
</li>
<li><p>If a component is mainly used by one feature but another feature needs it too, only move it to the <code>shared</code> folder if you expect more features to use it. Otherwise, keep it close to the feature it is most related to.</p>
</li>
</ul>
<h3 id="heading-should-we-always-use-interface-classes">Should we always use interface classes?</h3>
<blockquote>
<p>If you're not familiar with the concept, using interface classes allows you to register different implementations of a class without changing anything else in the app. For example, you can switch between a mock implementation and the real one for a service.</p>
</blockquote>
<p>I recently changed my mind on this. I used to always register my Managers and Services with an abstract interface. Unfortunately, the downside of using interfaces is that navigating through your code with <em>go to definition</em> becomes much more cumbersome, and any change in a method signature has to be made in two places.</p>
<p>Now, I only recommend using interfaces if you already know you will have more than one implementation of a class. Otherwise, don't use them. If you need it later, you can always refactor your code.</p>
<blockquote>
<p>Still waiting for the refactoring function: Extract interface, which many other languages offer in their editors.</p>
</blockquote>
<h2 id="heading-so-should-i-use-the-pfa-for-all-my-apps-in-future">So, should I use the PFA for all my apps in future?</h2>
<p>You can, and you'll probably enjoy building apps with it. However, my real goal is to help you understand that being a successful app developer isn't just about following common patterns or recipes. Instead, focus on learning why we choose a certain architecture. When you decide to use a specific pattern or package, don't do it just because you read a tutorial or because everyone says it's the right way on Youtube. Have your own understanding of why you use this approach and not another one.</p>
<p>Learn to think in terms of objects. Imagine how the objects your app creates float in memory, referencing each other. Unless you can do that, you'll unnecessarily limit your possibilities. It's also much more fun to design an app this way because it's a creative process rather than just following recipes.</p>
<p>Before deciding to use any proposed architectures or state management approaches, compare them. Clone a reference app and inspect its code:</p>
<ul>
<li><p>How easy is it for you to understand the project's structure?</p>
</li>
<li><p>Can you easily navigate between the UI and the data layer using the IDE?</p>
</li>
<li><p>Starting from pressing a button inside a widget, try to follow through what code gets executed.</p>
</li>
<li><p>Are you comfortable with the number of procedures necessary to wire everything up?</p>
</li>
<li><p>Write a very simple app using the approach that seemed most promising to you.</p>
</li>
</ul>
<h2 id="heading-the-official-flutter-reference-app-for-architecture-compass-in-pfa">The Official Flutter Reference App for Architecture: Compass in PFA</h2>
<p>Recently, the Flutter Team published a section on <a target="_blank" href="https://docs.flutter.dev/app-architecture">architecting a Flutter app</a> along with a <a target="_blank" href="https://github.com/flutter/samples/tree/main/compass_app">reference implementation</a>. If you've read this article so far, you might understand why I'm not entirely satisfied with that guide.</p>
<ul>
<li><p>In my opinion, using MVVM to describe that architecture is misleading because it doesn't align with the original concept of MVVM.</p>
</li>
<li><p>The project isn't organized by features, which makes navigation difficult.</p>
</li>
<li><p>The HTTP clients aren't set up to easily use the new native HTTP clients.</p>
</li>
<li><p>There are too many layers.</p>
</li>
<li><p>Error handling avoids exceptions and returns result objects everywhere (this could be a topic for a separate article).</p>
</li>
</ul>
<p>I took the time to completely restructure and rebuild the project following the patterns I describe in the PVA. You can find the <a target="_blank" href="https://github.com/escamoteur/compass_fork">refactored project here</a>.</p>
<p>How does it differ from the original version:</p>
<ul>
<li><p>The project is organized by features (there is a branch of the original that is just reorganized and fixes the HttpClients).</p>
</li>
<li><p>Use cases and repositories are combined into Manager objects.</p>
</li>
<li><p>Instead of using immutable business objects in the data layer, it uses the proxy approach explained in detail in my last two articles: <a target="_blank" href="https://blog.burkharts.net/understanding-the-problems-with-dogmatic-programming-advice">Understanding the Problems with Dogmatic Programming Advice</a> and <a target="_blank" href="https://blog.burkharts.net/keeping-widgets-in-sync-with-your-data">Keeping Widgets in Sync with Your Data</a>.</p>
</li>
<li><p>Instead of the simple command objects from the original, it uses my <a target="_blank" href="https://pub.dev/packages/flutter_command">flutter_command package</a>.</p>
</li>
<li><p>Consistent error handling uses exceptions below the manager layer, which are correctly routed by the commands.</p>
</li>
<li><p>State management is done using <a target="_blank" href="https://pub.dev/packages/watch_it">watch_it</a>/<a target="_blank" href="https://pub.dev/packages/get_it">get_it</a>.</p>
</li>
<li><p>HTTP clients are set up to use the new native clients, which <a target="_blank" href="https://blog.burkharts.net/everything-you-always-wanted-to-know-about-httpclients">greatly improves performance.</a></p>
</li>
</ul>
<p>Although this isn’t a perfect measure, it still shows that the refactored version is less complex.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>Original</td><td>PFA Version</td></tr>
</thead>
<tbody>
<tr>
<td>Total files analyzed:</td><td>111</td><td>94</td></tr>
<tr>
<td>Total lines of code:</td><td>9147</td><td>7804</td></tr>
<tr>
<td>Total classes:</td><td>130</td><td>110</td></tr>
<tr>
<td>Total functions:</td><td>304</td><td>267</td></tr>
<tr>
<td>Total enums:</td><td>2</td><td>2</td></tr>
</tbody>
</table>
</div><p>I recommend cloning both the original and the refactored versions and trying to navigate through the code to decide for yourself if you like the PFA approach.</p>
<h3 id="heading-recommended-additional-information">Recommended additional information</h3>
<h4 id="heading-on-proxyobjects-and-an-introduction-to-fluttercommand">On ProxyObjects and an introduction to flutter_command</h4>
<ul>
<li><p><a target="_blank" href="https://blog.burkharts.net/understanding-the-problems-with-dogmatic-programming-advice">https://blog.burkharts.net/understanding-the-problems-with-dogmatic-programming-advice</a></p>
</li>
<li><p><a target="_blank" href="https://blog.burkharts.net/keeping-widgets-in-sync-with-your-data">https://blog.burkharts.net/keeping-widgets-in-sync-with-your-data</a></p>
</li>
</ul>
<h4 id="heading-state-management-with-getit-and-watchit">State Management with get_it and watch_it</h4>
<ul>
<li><p><a target="_blank" href="https://blog.burkharts.net/one-to-find-them-all-how-to-use-service-locators-with-flutter">https://blog.burkharts.net/one-to-find-them-all-how-to-use-service-locators-with-flutter</a></p>
</li>
<li><p><a target="_blank" href="https://blog.burkharts.net/lets-get-this-party-started-startup-orchestration-with-getit">https://blog.burkharts.net/lets-get-this-party-started-startup-orchestration-with-getit</a></p>
</li>
<li><p><a target="_blank" href="https://medium.com/easy-flutter/flutter-the-state-management-with-watch-it-f66e8336e8f3">https://medium.com/easy-flutter/flutter-the-state-management-with-watch-it-f66e8336e8f3</a></p>
</li>
</ul>
<h4 id="heading-on-rvms">On RVMS</h4>
<p>a bit dated but might give additional insights that I might not have included here</p>
<ul>
<li><a target="_blank" href="https://medium.com/flutter-community/exploring-flutter-command-with-rvms-9f1962897a31">https://medium.com/flutter-community/exploring-flutter-command-with-rvms-9f1962897a31</a></li>
</ul>
<h4 id="heading-on-error-handling-with-commands">On error handling with commands</h4>
<ul>
<li><a target="_blank" href="https://www.droidcon.com/2023/08/07/coding-the-happy-path-with-commands-and-exceptions/">https://www.droidcon.com/2023/08/07/coding-the-happy-path-with-commands-and-exceptions/</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[One to find them all: How to use Service Locators with Flutter]]></title><description><![CDATA[ATTENTION: This is an article about get_it which has nothing to do with the getX package please stop mixing them up.

Most people, when starting with Flutter, look for a way to access their data from views to keep them separate. The Flutter documenta...]]></description><link>https://blog.burkharts.net/one-to-find-them-all-how-to-use-service-locators-with-flutter</link><guid isPermaLink="true">https://blog.burkharts.net/one-to-find-them-all-how-to-use-service-locators-with-flutter</guid><category><![CDATA[get_it]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[architecture]]></category><category><![CDATA[State Management ]]></category><dc:creator><![CDATA[Thomas Burkhart]]></dc:creator><pubDate>Sat, 22 Feb 2025 19:24:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/uAFjFsMS3YY/upload/869f27995d5153db30b6a83f0d1226ab.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>ATTENTION</strong>: This is an article about get_it which has nothing to do with the getX package please stop mixing them up.</p>
</blockquote>
<p>Most people, when starting with Flutter, look for a way to access their data from views to keep them separate. The Flutter documentation recommends using an <a target="_blank" href="https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html">InheritedWidget</a>. This not only allows data access from anywhere in the widget tree but also automatically updates widgets that reference it.</p>
<h3 id="heading-some-problems-with-inheritedwidgets">Some problems with InheritedWidgets</h3>
<p>The biggest problem for me when I wrote this article was that I couldn't get only the parts of the widget tree to rebuild where the data had changed. Additionally, the need to have access to the <code>BuildContext</code> limits its use to the UI part of your code.</p>
<blockquote>
<p>I now know that you can make it work, but I still wonder why struggle with InheritedWidgets when there are easier solutions. The requirement for the BuildContext still remains.</p>
</blockquote>
<h3 id="heading-alternatives">Alternatives</h3>
<p>Since the automatic updating of widgets that reference it isn't working perfectly, you might question why you should use an <code>InheritedWidget</code> at all. Especially if you're using the <code>InheritedWidget</code> just to access your model from anywhere, like when using BLOC or other reactive patterns, there are other solutions that might be even better.</p>
<h4 id="heading-singletons">Singletons</h4>
<p>Singletons might be the first thing that comes to mind. While a Singleton makes it easy to access an object from anywhere, it complicates unit testing because you can only create one instance, making it hard to mock.</p>
<h4 id="heading-ioc-containers-with-dependency-injection">IoC containers with Dependency Injection</h4>
<p>While this is a possible alternative for accessing an object and keeping it flexible for testing, I have some concerns about automatically injecting objects at runtime.</p>
<ul>
<li><p>For me, it makes it harder to track where a specific object instance comes from, but that might just be personal preference.</p>
</li>
<li><p>Using an IoC container creates a network of dependent objects, which means that when you access the first object, all dependent objects are instantiated at the same time. This can impact startup performance, especially for mobile apps. Even objects that might be needed later could be created unnecessarily. (I know some IoCs offer lazy creation, but this doesn't fully solve the problem.)</p>
</li>
<li><p>IoC containers usually require some form of reflection to determine which objects need to be injected where. Since Dart doesn't support this in Flutter, it can only be addressed using code generation tools.</p>
</li>
</ul>
<h4 id="heading-provider-riverpod">Provider / Riverpod</h4>
<p><a target="_blank" href="https://pub.dev/packages/provider">Provider</a> is a strong alternative to get_it. However, I still think get_it is a good choice for these reasons:</p>
<ul>
<li><p>Provider requires a <code>BuildContext</code> to access registered objects, so you can't use it inside business objects outside the Widget tree or in a pure Dart package.</p>
</li>
<li><p>Provider adds its own Widget classes to the widget tree that are not GUI elements but are needed to access the objects registered in Provider. Personally, I prefer to have as few non-UI Widgets in my widget trees as possible.</p>
</li>
<li><p>In general, the concept behind get_it is easier for many people to understand.</p>
</li>
<li><p>Although Riverpod is very powerful, its API is too complex, in my opinion.</p>
</li>
<li><p><strong>With</strong> <a target="_blank" href="https://pub.dev/packages/watch_it"><strong>watch_it</strong></a><strong>, get_it gets the simplest state management solution for Fluttter</strong></p>
</li>
</ul>
<blockquote>
<p>There are now ways to access objects from Provider without the BuildContext, but I still prefer get_it’s API and features.</p>
</blockquote>
<h4 id="heading-service-locators">Service Locators</h4>
<p>Like with IoCs, you need to register the types you want to access later. The difference is that instead of having an IoC inject instances automatically, you call the service locator directly to get the object you need.</p>
<p><em>Some people criticize this pattern, calling it old-fashioned and hard to test. However, as we will see, the testing concern isn't really valid. In my view, delivering software is more important than getting caught up in theoretical debates about the best pattern. For me and many others, Service Locators are a straightforward and practical solution.</em></p>
<p>A great advantage of using a Service Locator or IoC is that you are not restricted to using it inside a widget tree; you can use it anywhere to access any type of registered object.</p>
<h3 id="heading-getit-the-service-locator-for-dart">GetIt the Service Locator for Dart</h3>
<p>Coming from C#, I was used to a simple Service Locator (SL) called <a target="_blank" href="https://github.com/reactiveui/splat">Splat</a>. This inspired me to create something similar in Dart, resulting in the development of <a target="_blank" href="https://pub.dartlang.org/packages/get_it">GetIt</a>.</p>
<p><strong>GetIt is very fast</strong> because it uses a <code>Map&lt;Type&gt;</code> internally, allowing access in <strong>O(1)</strong> time.</p>
<p>GetIt is a singleton, so you can access it from anywhere using its <code>instance</code> property or its shortcut:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> getItInstance = GetIt.instance; 
<span class="hljs-comment">//shortcut </span>
<span class="hljs-keyword">final</span> getItInstance2 = GetIt.I;
</code></pre>
<h3 id="heading-usage">Usage</h3>
<p>It's pretty straightforward. Typically, at the start of your app, you register the types you want to access later from anywhere in your app. After that, you can access instances of the registered types by calling the Service Locator again.</p>
<p>The nice thing is you can register an interface or abstract class along with a concrete implementation. When accessing the instance, you always request the interface or abstract class type. This makes it easy to switch the implementation by simply changing the concrete type at registration time.</p>
<h3 id="heading-globals-strike-back-or-the-return-of-the-globals">Globals strike back or the Return of the Globals</h3>
<p>One major difference from C# is that Dart allows the use of global variables. Although GetIt is a singleton, I like to assign its instance to a global variable to make accessing GetIt easier.</p>
<p><em>I can almost hear some of you cringe at the mention of 'global variable,' especially if, like me, you were always told that globals are bad. Recently, I learned a much nicer term for them: 'Ambient variables.' This might sound like a euphemism, but it actually describes their purpose better. These are variables that hold object instances defining the environment in which the app operates.</em></p>
<blockquote>
<p>if you use my <a target="_blank" href="https://pub.dev/packages/watch_it"><code>watch_it</code></a> package you get a gloabal <code>di</code> instance to get_it included</p>
</blockquote>
<h3 id="heading-getting-practical">Getting practical</h3>
<p>I refactored a simple example to use GetIt instead of an inherited Widget. To set up the Service Locator, I added a new file called <code>di.dart</code>, which also contains the global (ambient) variable for the Service Locator. This makes it easier to reference when writing unit tests.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// ambient variable to access the service locator </span>
<span class="hljs-keyword">final</span> di = GetIt.instance;
<span class="hljs-keyword">void</span> setup() 
{ 
    di.registerSingleton(AppModel());
<span class="hljs-comment">// Alternatively you could write it GetIt.I.registerSingleton(AppModel()); </span>
}
</code></pre>
<blockquote>
<p>Someone recently pointed out that there is a conceptual difference between <strong>DI</strong> (Dependency Injection) and a Service Locator, and that I should not have named the global get_it instance <code>di</code>. In my view, this is more of an implementation detail because both DI and SL aim to achieve "inversion of control." This means you can control how an object or function behaves from the outside by injecting or locating objects.</p>
<p>Because of the well-known abbreviation DI for dependency injection, I continue to use <code>di</code> as the variable name. Feel free to use <code>sl</code>, <code>locator</code>, or whatever you prefer.</p>
</blockquote>
<p>GetIt offers various methods to register types. The <code>registerSingleton</code> method ensures you always receive the same instance of the registered object.</p>
<p>When using the InheritedWidget, the definition of a button looked like this:</p>
<pre><code class="lang-dart">MaterialButton( 
    child: Text(<span class="hljs-string">"Update"</span>), 
    onPressed: TheViewModel.of(context).update 
),
</code></pre>
<p>Now with GetIt it changes to</p>
<pre><code class="lang-dart">MaterialButton( 
    child: Text(<span class="hljs-string">"Update"</span>), 
    onPressed: di.<span class="hljs-keyword">get</span>&lt;AppModel&gt;().update
),
</code></pre>
<p>Actually, because GetIt is a callable class we can write</p>
<pre><code class="lang-dart">MaterialButton( 
    child: Text(<span class="hljs-string">"Update"</span>), 
    onPressed: di&lt;AppModel&gt;().update 
),
</code></pre>
<p>which is pretty concise.</p>
<p>you can find the whole code for the SL version of this App here <a target="_blank" href="https://github.com/escamoteur/flutter_weather_demo/tree/using_service_loactor">https://github.com/escamoteur/flutter_weather_demo/tree/using_service_loactor</a></p>
<p><strong><s>Extremely important if you use GetIt: ALWAYS use the same style to import your project files either as relative paths OR as package which I recommend. DON'T mix them because currently Dart treats types imported in different ways as two different types although both reference the same file.</s></strong></p>
<blockquote>
<p>This warning seems to be no longer necessary according to an issue in the Dart compiler. However, I would still choose to consistently use one method..</p>
</blockquote>
<h2 id="heading-registration-in-detail">Registration in Detail</h2>
<h3 id="heading-different-ways-of-registration">Different ways of registration</h3>
<p>Besides the above used <code>registerSingleton</code> there are two more ways to register types in GetIt</p>
<h4 id="heading-factory">Factory</h4>
<pre><code class="lang-dart">di.registerFactory( () =&gt; AppModelImplementation() );
</code></pre>
<p>If you register your type like this, each call to <code>di.get&lt;AppModel&gt;()</code> will create a new instance of <code>AppModelImplementation</code>, as long as it's a descendant of <code>AppModel</code>. To do this, you need to pass a factory function to <code>registerFactory</code>.</p>
<p>Sometimes, it's useful to pass different values to factories when calling <code>get()</code>. For this, there are versions of registering factories where the factory function takes two parameters:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">void</span> registerFactoryParam&lt;AppModel,<span class="hljs-built_in">String</span>,<span class="hljs-built_in">int</span>&gt;((title, size) =&gt; AppModelImplementation(title,size));
</code></pre>
<p>When requesting an instance you pass the values for those parameters:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> instance = di&lt;AppModel&gt;(param1: <span class="hljs-string">'abc'</span>,param2:<span class="hljs-number">3</span>);
</code></pre>
<h4 id="heading-lazysingleton">LazySingleton</h4>
<p>Creating the instance during registration can take time at app start-up. You can delay the creation until the object is requested for the first time using:</p>
<pre><code class="lang-dart">di.registerLazySingleton(() =&gt; AppModelImplementation());
</code></pre>
<p>Only the first time you call <code>get&lt;AppModel&gt;()</code>, the factory function you provided will be executed. After that, you will always receive the same instance.</p>
<h2 id="heading-applications-beyond-just-accessing-models-from-views">Applications beyond just accessing models from views</h2>
<p>When using a service locator with interfaces or abstract classes (I really wish Dart still had interfaces), you gain a lot of flexibility in configuring your app's behavior at runtime:</p>
<ul>
<li><p>Easily switch between different implementations of services. For example, define your REST API service class as an abstract class "WebAPI" and register it in the service locator with different implementations, such as various API providers or a mock class.</p>
</li>
<li><pre><code class="lang-dart">      <span class="hljs-keyword">if</span> (emulation) { 
          di.registerSingleton( WeatherAPIEmulation() ); 
      } <span class="hljs-keyword">else</span> { 
          di.registerSingleton(WeatherAPIOpenWeatherMap() ); 
      }
</code></pre>
</li>
<li><p>Register parts of your widget tree as builder factories in the SL, and choose different builders at runtime based on the screen size (phone/tablet).</p>
</li>
<li><p>If your business objects or services need to reference each other, register them in GetIt.</p>
</li>
</ul>
<h2 id="heading-testing-with-getit">Testing with GetIt</h2>
<p>Testing with GetIt is very easy because you can register a mock object instead of the real one and then run your tests.</p>
<p>GetIt offers a <code>reset()</code> method that clears all registered types, allowing you to start fresh in each test.</p>
<p>If you prefer to inject your mocks for the test, this pattern is recommended for objects that use the service locator:</p>
<pre><code class="lang-dart">AppModel([WeatherAPI? weatherAPI]): _weatherAPI = weatherAPI ?? di();
</code></pre>
<h2 id="heading-theres-more">There's more</h2>
<p>GetIt offers many more features than I've mentioned here. If you like GetIt, I recommend reading the GetIt <a target="_blank" href="https://pub.dev/documentation/get_it/latest/">Readme</a>, where you will find:</p>
<ul>
<li><p>Asynchronous Factories and Singletons</p>
</li>
<li><p>Functions to unregister or reset registered factories/singletons</p>
</li>
<li><p>How to create more than one instance of GetIt if needed</p>
</li>
<li><p>Registering multiple objects of the same type by name</p>
</li>
<li><p>Startup orchestration of your app. You can find more on this in my other post <a target="_blank" href="https://blog.burkharts.net/lets-get-this-party-started-startup-orchestration-with-getit">Let's get this party started</a></p>
</li>
<li><p>GetIt scopes, which make creating and disposing of objects based on your app's state very easy</p>
</li>
<li><p>And much <a target="_blank" href="https://pub.dev/packages/get_it">more...</a></p>
</li>
</ul>
<h3 id="heading-watchit">WatchIt</h3>
<p>GetIt makes accessing your objects from anywhere very easy. To make it the simplest state management solution for Flutter, you can use its companion package, <a target="_blank" href="https://pub.dev/packages/watch_it">watch_it</a>.</p>
<p>At <a target="_blank" href="https://github.com/escamoteur/flutter_weather_demo/tree/using_watch_it">https://github.com/escamoteur/flutter_weather_demo/tree/using_watch_it</a>, you can find a version of the demo app that uses watch_it instead of a StreamBuilder. After registering the <code>AppModel</code> as before, we can make the widget rebuild automatically every time the stream emits a new item.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WeatherListView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">WatchingWidget</span> </span>{
  WeatherListView();
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> snapshot = watchStream(
      (AppModel model) =&gt; model.WeatherStream,
    );
    <span class="hljs-keyword">if</span> (snapshot.hasData &amp;&amp; snapshot.data!.length &gt; <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">return</span> ListView.builder(
          itemCount: snapshot.data!.length,
          itemBuilder: (BuildContext context, <span class="hljs-built_in">int</span> index) =&gt;
              buildRow(context, index, snapshot.data!));
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> Text(<span class="hljs-string">"No items"</span>);
    }
  }
</code></pre>
<p><code>watchStream</code> accesses the <code>AppModel</code> inside GetIt and watches the <code>WeatherStream</code> property.</p>
<p>You can make widgets rebuild with watch_it by watching:</p>
<ul>
<li><p>Listenables / ValueListenables</p>
</li>
<li><p>Futures</p>
</li>
<li><p>Streams</p>
</li>
</ul>
<p>Additionally, you can do almost anything that would typically require a StatefulWidget inside a StatelessWidget, which helps reduce a lot of boilerplate code.</p>
]]></content:encoded></item><item><title><![CDATA[Keeping widgets in sync with your data]]></title><description><![CDATA[This is a follow-up to my last post on using proxy objects.This time we will:

Improve error handling

Show how to display a proxy in multiple places at once

Demonstrate how to use Commands to write elegant code


Improving the Error Handling of the...]]></description><link>https://blog.burkharts.net/keeping-widgets-in-sync-with-your-data</link><guid isPermaLink="true">https://blog.burkharts.net/keeping-widgets-in-sync-with-your-data</guid><category><![CDATA[#flutter_command]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[#dart language]]></category><category><![CDATA[architecture]]></category><category><![CDATA[patterns]]></category><category><![CDATA[get_it]]></category><category><![CDATA[watch_it]]></category><dc:creator><![CDATA[Thomas Burkhart]]></dc:creator><pubDate>Sun, 27 Oct 2024 13:16:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/yD5rv8_WzxA/upload/6af3e288513a178539221334a155eeb1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a follow-up to my last <a target="_blank" href="https://blog.burkharts.net/understanding-the-problems-with-dogmatic-programming-advice">post</a> on using proxy objects.<br />This time we will:</p>
<ul>
<li><p>Improve error handling</p>
</li>
<li><p>Show how to display a proxy in multiple places at once</p>
</li>
<li><p>Demonstrate how to use Commands to write elegant code</p>
</li>
</ul>
<h3 id="heading-improving-the-error-handling-of-the-isliked-property">Improving the Error Handling of the <code>isLiked</code> Property</h3>
<p>Interestingly, while implementing the demo code for today’s post, I realized that our previous solution for optimistic updates with undo in case of an error has a serious flaw. Let's take a second look at the code.</p>
<pre><code class="lang-dart">  <span class="hljs-built_in">bool</span> <span class="hljs-keyword">get</span> isLiked =&gt; _likeOverride ?? _target!.isLiked;
  <span class="hljs-built_in">bool?</span> _likeOverride;
  <span class="hljs-comment">/// <span class="markdown">optimistic UI update</span></span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; like(BuildContext context) <span class="hljs-keyword">async</span> {
    _likeOverride = <span class="hljs-keyword">true</span>;
    notifyListeners();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> di&lt;ApiClient&gt;().likePost(_target!.id);
    } <span class="hljs-keyword">catch</span> (e) {
      _likeOverride = <span class="hljs-keyword">null</span>;
      notifyListeners();
    }
  }
</code></pre>
<p>At first glance, this looks fine. If you rarely encounter errors when calling your API or if you frequently update your data from the backend, you might not notice the problem. Imagine the following sequence:</p>
<ol>
<li><p><code>Dto.isLiked == false</code> → <code>Proxy.isLiked == false</code></p>
</li>
<li><p>like → success → <code>_likeOverride == true</code> → <code>Proxy.isLiked == true</code></p>
</li>
<li><p>unlike → fail → <code>_likeOverride == null</code> → <code>Proxy.isLiked == false</code></p>
</li>
</ol>
<p>Just clearing <code>_likeOverride</code> loses the last successful state change. Luckily for a bool value undoing can be done by inverting the current value</p>
<pre><code class="lang-dart">    Future&lt;<span class="hljs-keyword">void</span>&gt; like(BuildContext context) <span class="hljs-keyword">async</span> {
    _likeOverride = <span class="hljs-keyword">true</span>;
    notifyListeners();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> di&lt;ApiClient&gt;().likePost(_target!.id);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-keyword">if</span> (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          <span class="hljs-keyword">const</span> SnackBar(
            content: Text(<span class="hljs-string">'Failed to like post'</span>),
          ),
        );
      }
      _likeOverride = !_likeOverride;
      notifyListeners();
    }
  }
</code></pre>
<p>We will only clear <code>_likeOverride</code> when we update the proxy's target from our API.</p>
<h2 id="heading-showing-proxies-in-more-than-one-place">Showing Proxies in more than one place</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727641206759/1838c23c-3ddf-4d42-93e3-a03570828652.gif" alt class="image--center mx-auto" /></p>
<p>We want to implement the above behavior. We aim to have two feeds of posts: one showing all our posts and the other showing only posts about dogs. Additionally, any changes to posts in one feed should automatically update in all other feeds displaying that post.</p>
<p>To achieve this, we use the same <code>PostFeedView</code> twice in a row and pass two different <code>DataSource</code> objects to each. One <code>DataSource</code> contains all posts, while the other contains only posts about dogs. Proxies of posts that should appear in both feeds are added to both <code>DataSources</code>. When reloading the data of a <code>DataSource</code>, we dispose all its proxies because they are <code>ChangeNotifiers</code>. This helps avoid memory leaks by disposing any proxy that is no longer used. But what if that proxy is still part of another <code>DataSource</code> displaying the post? If we simply dispose the proxy, the still active <code>PostCard</code> widget would no longer update when the data changes.</p>
<h3 id="heading-reference-counting-the-unsung-hero">Reference counting: the unsung hero</h3>
<p>To solve this, we add a reference counter to every <code>PostProxy</code>.</p>
<ul>
<li><p>When we load a new <code>PostDto</code> for the first time, we create a new proxy and store it in our proxy repository.</p>
</li>
<li><p>If we load a DTO that already has a proxy in our repository, we update the target of the proxy.</p>
</li>
<li><p>When we add the proxy to any <code>DataSource</code>, we increase the reference counter.</p>
</li>
<li><p>When we remove a proxy from a <code>DataSource</code>, we decrement the reference counter.</p>
</li>
<li><p>If the reference counter reaches 0, we dispose the proxy and remove it from the registry.</p>
</li>
</ul>
<blockquote>
<p>If we would add a details view for a post, we would increment the reference count before pushing the details page and decrement it when popping it</p>
</blockquote>
<p>Let’s see the needed parts (some lines left out for better clarity):</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostProxy</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ChangeNotifier</span> </span>{
  <span class="hljs-keyword">late</span> PostDto _target;
  <span class="hljs-built_in">int</span> referenceCount = <span class="hljs-number">0</span>;

  PostProxy(<span class="hljs-keyword">this</span>._target);

  <span class="hljs-comment">//setter for the target</span>
  <span class="hljs-keyword">set</span> target(PostDto value) {
    _upDateTarget(value);
  }

  <span class="hljs-keyword">void</span> _upDateTarget(PostDto postDto) {
    _likeOverride = <span class="hljs-keyword">null</span>;
    _target = postDto;
    notifyListeners();
  }
  <span class="hljs-keyword">void</span> updateFromApi() {
    di&lt;ApiClient&gt;().getPost(_target!.id).then((postDto) {
      _upDateTarget(postDto);
    });
  }
</code></pre>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostManagerImplementation</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">PostManager</span> </span>{
  <span class="hljs-comment">/// <span class="markdown">repository for already proxied posts</span></span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">int</span>, PostProxy&gt; _proxyRepository = {};

  <span class="hljs-meta">@override</span>
  PostProxy createProxy(PostDto postDto) {
    <span class="hljs-keyword">if</span> (_proxyRepository.containsKey(postDto.id)) {
      <span class="hljs-keyword">final</span> existingProxy = _proxyRepository[postDto.id]!;
      existingProxy.target = postDto;
      existingProxy.referenceCount++;
      <span class="hljs-keyword">return</span> existingProxy;
    }
    <span class="hljs-keyword">final</span> newProxy = PostProxy(postDto);
    newProxy.referenceCount++;
    _proxyRepository[postDto.id] = newProxy;
    <span class="hljs-keyword">return</span> newProxy;
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> releaseProxies(<span class="hljs-built_in">List</span>&lt;PostProxy&gt; proxies) {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> proxy <span class="hljs-keyword">in</span> proxies) {
      releaseProxy(proxy);
    }
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> releaseProxy(PostProxy proxy) {
    proxy.referenceCount--;
    <span class="hljs-keyword">if</span> (proxy.referenceCount == <span class="hljs-number">0</span>) {
      _proxyRepository.remove(proxy.id);
      proxy.dispose();
    }
  }
}
</code></pre>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FeedDataSource</span> </span>{
  FeedDataSource({<span class="hljs-keyword">this</span>.filter});
  <span class="hljs-comment">/// <span class="markdown">select which posts to show in this feed</span></span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">bool</span> <span class="hljs-built_in">Function</span>(PostDto postDto)? filter;

  <span class="hljs-keyword">final</span> _posts = &lt;PostProxy&gt;[];
  <span class="hljs-keyword">final</span> ValueNotifier&lt;<span class="hljs-built_in">int</span>&gt; _postsCount = ValueNotifier(<span class="hljs-number">0</span>);
  ValueListenable&lt;<span class="hljs-built_in">int</span>&gt; <span class="hljs-keyword">get</span> postsCount =&gt; _postsCount;
  <span class="hljs-keyword">final</span> ValueNotifier&lt;<span class="hljs-built_in">bool</span>&gt; _isLoading = ValueNotifier(<span class="hljs-keyword">false</span>);
  ValueListenable&lt;<span class="hljs-built_in">bool</span>&gt; <span class="hljs-keyword">get</span> isLoading =&gt; _isLoading;

  PostProxy getPostAtIndex(<span class="hljs-built_in">int</span> index) {
    <span class="hljs-keyword">assert</span>(index &gt;= <span class="hljs-number">0</span> &amp;&amp; index &lt; _posts.length);
    <span class="hljs-keyword">return</span> _posts[index];
  }

  <span class="hljs-keyword">void</span> updateData() <span class="hljs-keyword">async</span> {
    _isLoading.value = <span class="hljs-keyword">true</span>;
    <span class="hljs-keyword">final</span> postDtos = <span class="hljs-keyword">await</span> di&lt;ApiClient&gt;().getPosts();
    <span class="hljs-comment">/// <span class="markdown">decrement the reference count of all proxies</span></span>
    <span class="hljs-comment">/// <span class="markdown">and release the ones that are not anywhere else</span></span>
    di&lt;PostManager&gt;().releaseProxies(_posts);
    _posts.clear();
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> postDto <span class="hljs-keyword">in</span> postDtos) {
      <span class="hljs-keyword">if</span> (filter != <span class="hljs-keyword">null</span> &amp;&amp; !filter!(postDto)) {
        <span class="hljs-keyword">continue</span>;
      }
      _posts.add(di&lt;PostManager&gt;().createProxy(postDto));
    }
    _isLoading.value = <span class="hljs-keyword">false</span>;
    _postsCount.value = _posts.length;
  }
}
</code></pre>
<p>Check out the full source on <a target="_blank" href="https://github.com/escamoteur/proxy_pattern_demo/tree/reference_counting_proxy">https://github.com/escamoteur/proxy_pattern_demo/tree/reference_counting_proxy</a></p>
<h2 id="heading-adding-fluttercommands-and-functionallistener">Adding <code>flutter_commands</code> and <code>functional_listener</code></h2>
<p>If you've followed along so far, you've already grasped the main message of this post. The following section shows how we can add more functionality without much additional code.</p>
<p>As we can see above, we needed a separate <code>ValueNotifier</code> <code>_isLoading</code> to display a loading spinner while the <code>DataSource</code> loads new data. We would need to do this for every async call that should show a loading state. Often, we also want to control when a certain action can be executed or prevent triggering another refresh while one is still running. Additionally, the way we currently display the snackbar in case of an error is far from ideal and repetitive.</p>
<p>By using my <a target="_blank" href="https://pub.dev/packages/flutter_command"><code>flutter_command</code></a> and <a target="_blank" href="https://pub.dev/packages/functional_listener"><code>functional_listener</code></a> packages, we can make our lives much easier. You will see that it fits perfectly into the pattern of <a target="_blank" href="https://pub.dev/packages/watch_it"><code>watch_it</code></a> that you have already seen.</p>
<p><code>Commands</code> are objects that wrap a function and offer many observable properties. We will explore only a few here; please refer to the readme for details. The most notable ones are:</p>
<ul>
<li><p><code>isExecuting</code> - will be <code>true</code> while the wrapped function runs.</p>
</li>
<li><p><code>canExecute</code> - shows if a command can be executed at the moment and can ideally be used to activate or deactivate buttons. Its state is <code>!isExecuting &amp;&amp; restriction</code> - restriction is an input <code>Listenable&lt;bool&gt;</code> that you can pass when creating a command.</p>
</li>
<li><p><code>errors</code> - a <code>Listenable</code> where all exceptions that might be thrown by the wrapped function will be published. This allows you to keep the wrapped function free of any <code>try-catch</code> blocks and lets you observe errors from outside the command.</p>
</li>
</ul>
<p><code>functional_listener</code> - provides filter and merging functions for <code>ValueListenable</code>, allowing you to create logic networks to express behavior in a clear way. You'll understand this better when we look at the different parts of the final app version. You can find the full source at <a target="_blank" href="https://github.com/escamoteur/proxy_pattern_demo/tree/using_flutter_commands">https://github.com/escamoteur/proxy_pattern_demo/tree/using_flutter_commands</a></p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostProxy</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ChangeNotifier</span> </span>{
  --- lot left out ---
  <span class="hljs-comment">/// <span class="markdown">create a combined ValueListenable based on the updateDataCommands  </span></span>
  <span class="hljs-comment">/// <span class="markdown">and local updateFromApiCommand</span></span>
  <span class="hljs-keyword">late</span> ValueListenable&lt;<span class="hljs-built_in">bool</span>&gt; commandRestrictions = di&lt;PostManager&gt;()
      .updateFromApiIsExecuting
      .combineLatest(updateFromApiCommand.isExecuting, (a, b) =&gt; a || b);

  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> updateFromApiCommand = Command.createAsyncNoParamNoResult(
    () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> postDto = <span class="hljs-keyword">await</span> di&lt;ApiClient&gt;().getPost(_target!.id);
      _updateTarget(postDto);
    },
    <span class="hljs-comment">// block the command if any updateDataCommand is executing</span>
    restriction: di&lt;PostManager&gt;().updateFromApiIsExecuting,
  );

  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> likeCommand = Command.createAsyncNoParamNoResult(
    () <span class="hljs-keyword">async</span> {
      <span class="hljs-comment">/// <span class="markdown">optimistic UI update</span></span>
      _likeOverride = <span class="hljs-keyword">true</span>;
      notifyListeners();
      <span class="hljs-keyword">await</span> di&lt;ApiClient&gt;().likePost(_target!.id);
    },
    <span class="hljs-comment">// block the command if we update from the api</span>
    restriction: commandRestrictions,
    <span class="hljs-comment">// we want that the error is handled locally and globally in TheAppImplementation</span>
    errorFilter: <span class="hljs-keyword">const</span> ErrorHandlerLocalAndGlobal(),
  )..errors.listen(
      (e, _) {
        <span class="hljs-comment">// reverse the optimistic UI update</span>
        _likeOverride = !_likeOverride!;
        notifyListeners();
      },
    );
--- more left out ---
</code></pre>
<p>By passing <code>commandRestrictions</code> to the like and unlike commands as <code>restriction</code>, we automatically disable these commands whenever an update call to the API occurs.</p>
<p>To show this in the UI, we define our own <code>CommandIconButton</code>.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CommandIconButton</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">WatchingWidget</span> </span>{
  <span class="hljs-keyword">const</span> CommandIconButton({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.command,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.icon,
  });

  <span class="hljs-keyword">final</span> Command command;
  <span class="hljs-keyword">final</span> IconData icon;

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> canExecute = watch(command.canExecute).value;
    <span class="hljs-keyword">return</span> IconButton(
      onPressed: canExecute ? command.execute : <span class="hljs-keyword">null</span>,
      icon: Icon(icon),
    );
  }
}
</code></pre>
<p>The full <code>PostCard</code> now looks like:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostCard</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">WatchingWidget</span> </span>{
  <span class="hljs-keyword">const</span> PostCard({
    <span class="hljs-keyword">super</span>.key,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.post,
  });

  <span class="hljs-keyword">final</span> PostProxy post;

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-comment">/// <span class="markdown">watch the post to rebuild the widget when the post changes</span></span>
    watch(post);
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">bool</span> isLoading = watch(post.updateFromApiCommand.isExecuting).value;
    <span class="hljs-keyword">return</span> Card.outlined(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          AspectRatio(
            aspectRatio: <span class="hljs-number">16</span> / <span class="hljs-number">9</span>,
            child: !isLoading
                ? Image.network(post.imageUrl)
                : <span class="hljs-keyword">const</span> Center(child: CircularProgressIndicator()),
          ),
          Padding(
            padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">8.0</span>),
            child: Text(post.title),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              <span class="hljs-keyword">if</span> (post.isLiked)
                CommandIconButton(
                  icon: Icons.favorite,
                  command: post.unlikeCommand,
                )
              <span class="hljs-keyword">else</span>
                CommandIconButton(
                  icon: Icons.favorite_border,
                  command: post.likeCommand,
                ),
              <span class="hljs-keyword">const</span> SizedBox(width: <span class="hljs-number">8</span>),
              CommandIconButton(
                command: post.updateFromApiCommand,
                icon: Icons.refresh,
              ),
            ],
          ),
        ],
      ),
    );
  }
}
</code></pre>
<blockquote>
<p>In a real project I would probably convert the like/unlike Command to a toggleLike Command</p>
</blockquote>
<p>As you can see, we no longer have snackbar code inside the <code>PostCard</code>. Commands enable configurable error routing, as seen in the <code>PostProxy</code>. Locally, we have handlers to reset the <code>_optimisticLike</code>, while all exceptions are forwarded to the global error handler of the Command class:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TheAppImplementation</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TheApp</span> </span>{
  TheAppImplementation() {
    Command.globalExceptionHandler = (e, s) {
      _errorMessages.add(e.error.toString());
    };
  }

  <span class="hljs-keyword">final</span> StreamController&lt;<span class="hljs-built_in">String</span>&gt; _errorMessages =
      StreamController&lt;<span class="hljs-built_in">String</span>&gt;.broadcast();

  <span class="hljs-meta">@override</span>
  Stream&lt;<span class="hljs-built_in">String</span>&gt; <span class="hljs-keyword">get</span> errorMessagesStream =&gt; _errorMessages.stream;
}
</code></pre>
<p>To display the snackbar, we add a <code>StreamHandler</code> to our <code>HomePage</code>. This is the right way to show errors from the data layer without storing a navigator key somewhere:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomePage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">WatchingWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-comment">/// <span class="markdown">handler to display error messages</span></span>
    registerStreamHandler(
        select: (TheApp app) =&gt; app.errorMessagesStream,
        handler: (context, snapShot, _) {
          <span class="hljs-keyword">if</span> (snapShot.hasData) {
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text(snapShot.data!),
            ));
          }
        });
    <span class="hljs-keyword">return</span> Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Expanded(child: PostFeedView(dataSource: di&lt;PostManager&gt;().postsFeed)),
        Expanded(
            child: PostFeedView(dataSource: di&lt;PostManager&gt;().dogPostsFeed)),
      ],
    );
  }
}
</code></pre>
<h3 id="heading-bonus-using-an-undoablecommand">Bonus - using an UndoableCommand</h3>
<p>To undo optimistic data changes for a boolean property, you can easily define an error handler and negate the latest value. However, if you do optimistic updates for other types, this method won't work because you won't know the original value before the change in case of an error. For this, you can use another type of Command called the UndoableCommand. The function you wrap inside the command receives an undo stack of the type you want to undo when the Command is executed. The function can then push any state it might need to undo onto this stack. If an exception occurs during command execution, the <code>undo</code> function of the command receives this same undo stack, giving it access to the original data.</p>
<pre><code class="lang-dart">  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> toggleLikeCommand = Command.createUndoableNoParamNoResult&lt;<span class="hljs-built_in">bool</span>&gt;(
    (undoStack) <span class="hljs-keyword">async</span> {
      <span class="hljs-comment">/// <span class="markdown">save current state</span></span>
      undoStack.push(isLiked);

      _likeOverride = !isLiked;
      notifyListeners();
      <span class="hljs-keyword">if</span> (_likeOverride!) {
        <span class="hljs-keyword">await</span> di&lt;ApiClient&gt;().likePost(_target!.id);
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">await</span> di&lt;ApiClient&gt;().unlikePost(_target!.id);
      }
    },
    undo: (undoStack, reason) {
      _likeOverride = undoStack.pop();
      notifyListeners();
    },
    <span class="hljs-comment">// block the command if we update from the api</span>
    restriction: commandRestrictions,
    <span class="hljs-comment">// we want that the error is handled locally and globally in TheAppImplementation</span>
    errorFilter: <span class="hljs-keyword">const</span> ErrorHandlerGlobalIfNoLocal(),
  );
</code></pre>
<p>I also changed the command to a <code>toggleLike</code> version which makes its use even easier.</p>
<p>You can find this version here: <a target="_blank" href="https://github.com/escamoteur/proxy_pattern_demo/tree/using_flutter_commands">https://github.com/escamoteur/proxy_pattern_demo/tree/using_flutter_commands</a></p>
<p>I hope this gives you an idea of what you can achieve by adding <code>Commands</code> to your app. Have fun exploring the demo app's code and comparing the different implementations of the four branches.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding the Problems with Dogmatic Programming Advice]]></title><description><![CDATA[Okay, this will be a pretty opinionated article, but it's a topic I feel strongly about. I grew up with Object-Oriented Programming (OOP), and since then, I've thought of objects when designing software systems. At the core of OOP is the idea that ob...]]></description><link>https://blog.burkharts.net/understanding-the-problems-with-dogmatic-programming-advice</link><guid isPermaLink="true">https://blog.burkharts.net/understanding-the-problems-with-dogmatic-programming-advice</guid><category><![CDATA[watch_it]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Flutter Examples]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[immutability]]></category><category><![CDATA[get_it]]></category><dc:creator><![CDATA[Thomas Burkhart]]></dc:creator><pubDate>Sun, 22 Sep 2024 16:08:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727010924626/9894d462-e669-49f0-87b2-ba428c11cd52.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Okay, this will be a pretty opinionated article, but it's a topic I feel strongly about. I grew up with Object-Oriented Programming (OOP), and since then, I've thought of objects when designing software systems. At the core of OOP is the idea that objects hold data, which is modified by the methods and functions of the class interface. This paradigm fits well with Flutter's pattern of using a <code>Listenable</code> interface together with a <code>ListenableBuilder</code>, which updates the UI displaying the properties of an object every time that object changes.</p>
<p>In fact, if you remove the ability to change the state of an object by calling its functions, you lose much of the beauty and elegance of OOP. To achieve the same functionality with immutable data structures, you have to write a lot more code, which is harder to understand and less efficient.</p>
<h1 id="heading-but-but-mutable-shared-data-is-dangerous">But…, but mutable shared data is dangerous</h1>
<p>I can already hear half of Reddit shouting this when arguing for mutable objects.</p>
<ul>
<li><p>Yes, having mutable objects in your system can lead to potential problems that are hard to debug, especially if you're working in a larger team with less experienced developers.</p>
</li>
<li><p>It can be even more problematic in a multithreaded environment where concurrent changes to the same object could occur. However, in Dart and Flutter, we don't have to worry about this since we don't share data across isolates.</p>
</li>
<li><p>When working with async code, you might encounter situations where your data changes in an unexpected order.</p>
</li>
</ul>
<p>I'm not a big fan of Uncle Bob in general, especially his claim on the term "clean" for his monstrosity of architecture. However, he makes an interesting point in one of his YouTube talks. He notes that the real problem in the software industry is its rapid growth, which requires more developers each year. This leads to a large percentage of inexperienced developers in our field, which inevitably affects the quality of the code produced.</p>
<p>Because of this, we often see new best practices introduced and recommended to reduce potential pitfalls while programming.<br />The best-known examples in recent years are Test Driven Development (TDD) and immutability. Yes, following these concepts can help improve the quality of your software, but sometimes at a significant cost. I'll save TDD for another article and focus only on immutability.</p>
<p>Coming from purely functional programming languages, where immutability makes a lot of sense because every function always returns only copies of the data, the idea was promoted as the ultimate solution to the worst programming nightmares. Unfortunately, as with almost all dogmatic movements, people stopped asking if it makes sense to follow the new rule at all costs and in every situation.</p>
<p>Although Flutter follows the MVU principle from Elm, which can be seen as a function where new data produces a new UI, it is actually a hybrid system designed to update only the minimum part of the UI for top performance. Specifically, the observer pattern that you can implement using <code>ChangeNotifiers</code> doesn't really make sense with immutable objects because if nothing changes, what should be notified? How do you implement minimal rebuilding of the UI when using immutable objects? How do you implement automatic updates of different parts of your UI that observe the same data?</p>
<p>One solution is to use <code>Streams</code> or <code>ValueNotifiers</code> (as a Stream replacement) to send immutable message objects to the widgets you want to change. However, in many cases, the same goal can be achieved with simpler and more readable code by using mutable objects.</p>
<h1 id="heading-so-no-immutability">So no immutability?</h1>
<p>No, don't misunderstand me. I see the benefits of immutable data. But sometimes the goal isn't immutability; it's about having control over who can change the data and when it can be changed.</p>
<ul>
<li><p>Make only the members of an object public if they truly need to be public. Often, you can use an abstract interface to hide internal behavior, so users are less tempted to change the state directly from the outside.</p>
</li>
<li><p>Using <code>final</code> variables in methods and properties that should not be changed is beneficial. Besides preventing accidental changes, it communicates that this variable won't change.</p>
</li>
<li><p>Expose properties of objects only through public getters and modify internal object state only via methods or commands. This way, you can add logging or set breakpoints to see who is changing the object. I'm not a big fan of setters because they can hide additional actions beyond just assigning a value.</p>
</li>
<li><p>Use the Proxy pattern, which the rest of this article will explore.</p>
</li>
</ul>
<h1 id="heading-using-proxy-objects-to-get-the-best-of-both-worlds">Using Proxy objects to get the best of both worlds</h1>
<p>Very often, we receive Data Transfer Objects (DTOs) from remote APIs in the form of JSON responses. These class definitions are usually generated from something like OpenAPI, so adding extra functionality directly to these DTOs is impractical. I recommend treating DTOs from your backend as immutable data, regardless of whether you enforce that immutability at the code level or not. (I never really understood why a language needs explicit Data classes...)</p>
<p>Let's assume we are writing a social media app that allows users to scroll through feeds of various posts. In the example project for this article, the DTO looks like this:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostDto</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> id;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> title;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> imageUrl;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">bool</span> isLiked;

  PostDto({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.id,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.title,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.imageUrl,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.isLiked,
  });

  <span class="hljs-keyword">factory</span> PostDto.fromJson(<span class="hljs-built_in">int</span> id, <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; json) {
    <span class="hljs-keyword">return</span> PostDto(
      id: id,
      title: json[<span class="hljs-string">'title'</span>],
      imageUrl: json[<span class="hljs-string">'imageUrl'</span>],
      isLiked: json[<span class="hljs-string">'isLiked'</span>],
    );
  }
}
</code></pre>
<p>Instead of directly interacting with this object we wrap it inside a <code>PostProxy</code></p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostProxy</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ChangeNotifier</span> </span>{
  <span class="hljs-keyword">late</span> PostDto _target;

  PostProxy(<span class="hljs-keyword">this</span>._target);

  <span class="hljs-built_in">String</span> <span class="hljs-keyword">get</span> title =&gt; _target.title;
  <span class="hljs-built_in">String</span> <span class="hljs-keyword">get</span> imageUrl =&gt; _target.imageUrl;
  <span class="hljs-built_in">bool</span> <span class="hljs-keyword">get</span> isLiked =&gt; _target.isLiked;
}
</code></pre>
<p>This is the simplest version. Even though it is not immutable, the proxy's interface prevents any modification of the DTO.</p>
<h2 id="heading-making-the-proxy-a-living-object">Making the proxy a living object</h2>
<p>To fully utilize the power of the proxy, we need to add a bit more logic to it:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostProxy</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ChangeNotifier</span> </span>{
  <span class="hljs-keyword">late</span> PostDto _target;

  PostProxy(<span class="hljs-keyword">this</span>._target);

  <span class="hljs-built_in">String</span> <span class="hljs-keyword">get</span> title =&gt; _target.title;
  <span class="hljs-built_in">String</span> <span class="hljs-keyword">get</span> imageUrl =&gt; _target.imageUrl;
  <span class="hljs-built_in">bool</span> <span class="hljs-keyword">get</span> isLiked =&gt; _likeOverride ?? _target.isLiked;
  <span class="hljs-built_in">bool?</span> _likeOverride;

  <span class="hljs-keyword">void</span> updateFromApi() {
    di&lt;ApiClient&gt;().getPost(_target!.id).then((postDto) {
      _target = postDto;
      _likeOverride = <span class="hljs-keyword">null</span>;
      notifyListeners();
    });
  }

  <span class="hljs-comment">/// <span class="markdown">optimistic UI update</span></span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; like(BuildContext context) <span class="hljs-keyword">async</span> {
    _likeOverride = <span class="hljs-keyword">true</span>;
    notifyListeners();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> di&lt;ApiClient&gt;().likePost(_target!.id);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-keyword">if</span> (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          <span class="hljs-keyword">const</span> SnackBar(
            content: Text(<span class="hljs-string">'Failed to like post'</span>),
          ),
        );
      }
      _likeOverride = <span class="hljs-keyword">null</span>;
      notifyListeners();
    }
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; unlike(BuildContext context) <span class="hljs-keyword">async</span> {
    _likeOverride = <span class="hljs-keyword">false</span>;
    notifyListeners();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> di&lt;ApiClient&gt;().unlikePost(_target!.id);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-keyword">if</span> (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          <span class="hljs-keyword">const</span> SnackBar(
            content: Text(<span class="hljs-string">'Failed to unlike post'</span>),
          ),
        );
      }
      _likeOverride = <span class="hljs-keyword">null</span>;
      notifyListeners();
    }
  }
}
</code></pre>
<p>With this, the Proxy can respond to user input with an immediate UI update and gracefully recover from network issues during like/unlike operations.<br />(Please excuse the simple way I show the SnackBar)</p>
<blockquote>
<p>after publishing this article I realized that the above logic to undo the optimistc update has a flaw. Please check the second part of this post where I explain and fix it.</p>
</blockquote>
<p>In the UI, this Proxy can be used with a <code>ListenableBuilder</code> or, in the case of the Demo App, by using my <code>watch_it</code> package:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostCard</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">WatchingWidget</span> </span>{
  <span class="hljs-keyword">const</span> PostCard({
    <span class="hljs-keyword">super</span>.key,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.post,
  });

  <span class="hljs-keyword">final</span> PostProxy post;

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-comment">/// <span class="markdown">watch the post to rebuild the widget when the post changes</span></span>
    watch(post);
    <span class="hljs-keyword">return</span> Card.outlined(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          AspectRatio(aspectRatio: <span class="hljs-number">16</span> / <span class="hljs-number">9</span>, child: Image.network(post.imageUrl)),
          Padding(
            padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">8.0</span>),
            child: Text(post.title),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              <span class="hljs-keyword">if</span> (post.isLiked)
                IconButton(
                  icon: <span class="hljs-keyword">const</span> Icon(Icons.favorite),
                  onPressed: () =&gt; post.unlike(context),
                )
              <span class="hljs-keyword">else</span>
                IconButton(
                  icon: <span class="hljs-keyword">const</span> Icon(Icons.favorite_border),
                  onPressed: () =&gt; post.like(context),
                ),
              <span class="hljs-keyword">const</span> SizedBox(width: <span class="hljs-number">8</span>),
              IconButton(
                  onPressed: post.updateFromApi,
                  icon: <span class="hljs-keyword">const</span> Icon(Icons.refresh)),
            ],
          ),
        ],
      ),
    );
  }
}
</code></pre>
<p>The use of a nullable <code>override</code> version of a property to store a data update by the user until we refresh data from the backend can be applied to any property of your business objects.</p>
<p>You can find the full source code for the example app here: <a target="_blank" href="https://github.com/escamoteur/proxy_pattern_demo">https://github.com/escamoteur/proxy_pattern_demo</a>.</p>
<p><a target="_blank" href="https://blog.burkharts.net/keeping-widgets-in-sync-with-your-data">In the second part of this article</a>, I will show you how to use this pattern to propagate changes to proxy objects that are observed from different parts of the UI and need to have different lifetimes depending on where they are displayed. Also, we will replace the proxies functions with Commands.</p>
<p>I’m curious how the same demo app would look if written with pure immutable objects. If anyone would like to demonstrate this with a similar small amount of code, please post your fork in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[Everything You Always Wanted to Know About HttpClients]]></title><description><![CDATA[In case you are wondering about the cover of this post, I had to share with you the incredible location I was writing this post

When starting out writing apps with Flutter or Dart, you probably, like me, just followed the examples on how to make HTT...]]></description><link>https://blog.burkharts.net/everything-you-always-wanted-to-know-about-httpclients</link><guid isPermaLink="true">https://blog.burkharts.net/everything-you-always-wanted-to-know-about-httpclients</guid><category><![CDATA[Flutter]]></category><category><![CDATA[http]]></category><category><![CDATA[http2]]></category><category><![CDATA[httpclient]]></category><category><![CDATA[Dart]]></category><dc:creator><![CDATA[Thomas Burkhart]]></dc:creator><pubDate>Wed, 03 Jul 2024 17:42:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719936179377/c7dada7f-dee2-43cf-8091-f903c12614b5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>In case you are wondering about the cover of this post, I had to share with you the incredible location I was writing this post</p>
</blockquote>
<p>When starting out writing apps with Flutter or Dart, you probably, like me, just followed the examples on how to make HTTP requests and didn't think much about it. You might have noticed in the API docs of the global functions like <code>get</code>, <code>post</code>, etc., of the <strong>http</strong> package that it mentions that if you call the same server multiple times, it would be more efficient to reuse the same <code>HttpClient</code>. Unfortunately, this typically leads to more questions, which started me on an excursion into the wonders of HTTP implementations in Dart and Flutter.</p>
<p>This post will try to answer the following questions:</p>
<ol>
<li><p>How many HttpClients should you use and why?</p>
</li>
<li><p>Should you close your Clients?</p>
</li>
<li><p>Why should you use the new native HttpClient implementations?</p>
</li>
<li><p>How to ensure Flutter uses the correct Clients?</p>
</li>
<li><p>Pitfalls to avoid</p>
</li>
</ol>
<h2 id="heading-how-many-httpclients-should-an-app-use">How many HttpClients should an App use?</h2>
<p>If you spend some time analyzing the network traffic of your app, which I highly recommend, with either the Network Tool of the Dart Development Tools or a professional proxy like <a target="_blank" href="https://www.telerik.com/download/fiddler-everywhere">Fiddler</a> or <a target="_blank" href="https://www.charlesproxy.com/">Charles</a> you will see that a request consists of two phases:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719936848216/4b7460ff-0e70-4719-aca5-959bd00ae8d2.png" alt class="image--center mx-auto" /></p>
<p>The Request and the Response phase. When you look at the timings, you'll notice that the request phase takes quite a long time. During this period, no data is transmitted to your app. In fact, most of this time is spent with</p>
<ul>
<li><p>DNS resolution (35ms in this example)</p>
</li>
<li><p>TCP connect time (31ms)</p>
</li>
<li><p>HTTPS handshake (42ms)</p>
</li>
</ul>
<p>If we look at some of the requests at the startup of our current app you will see:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719937030888/0494369b-1532-480b-b3f8-1f372522a51c.png" alt class="image--center mx-auto" /></p>
<p>You see a lot of blue bars, which means that for every request with a new HttpClient, a new connection needs to be established, taking up a lot of time.</p>
<h3 id="heading-but-what-about-http-keep-alive-headers">But what about HTTP keep-alive headers?</h3>
<p>Indeed, since HTTP 1.1, clients can tell the server to keep a connection alive, which avoids the time needed to establish a new connection. However:</p>
<ul>
<li><p>This only works if you use the same HttpClient in Dart for all connections.</p>
</li>
<li><p>Even with keep-alive, one HTTP 1.1 connection can only be used once at a time. So, when you send many async requests, like at startup, don't expect them all to use the same connection. You would have to send them sequentially, which is likely slower than establishing additional connections in parallel.</p>
</li>
</ul>
<h3 id="heading-reusing-the-same-client-everywhere-in-your-app">Reusing the same Client everywhere in your app</h3>
<p>The most straightforward approach is to register one client instance in a place in your app where you can easily access it, such as using <a target="_blank" href="https://pub.dev/packages/get_it">get_it</a> (disclaimer: I'm the author of get_it) or any other service locator. Then, use that client instance throughout your app (we will later see that this isn't as straightforward in Flutter as you might think). This also means no longer using the global HTTP functions of the <strong>http</strong> package but instead using the methods of the client instance.</p>
<p>After we manage to do this for all requests in our app the image already starts to change:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719937089883/2f5d8346-9622-4f9f-97dc-6488d45017b9.png" alt class="image--center mx-auto" /></p>
<p><img src="timing-http1.1-one-client.png" alt /></p>
<p>We still see many blue bars indicating new connections being created, but we also see some orange bars with little or no blue following them. This means they are using already established connections. Comparing the total time for all requests, we reduced the time by more than 10 seconds, which is remarkable since we only changed the reuse of HttpClients. One reason we didn't gain more is that we send many image requests to our imgix server in parallel.</p>
<h3 id="heading-http2-to-the-rescue">HTTP/2 to the rescue</h3>
<p>So far we always used the standard Dart HttpClient implementation that is part of the SDK. Let's have a closer look at the requests:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719937121212/ce3b4c91-1a06-4e3f-9948-05d6cc57d4dd.png" alt class="image--center mx-auto" /></p>
<p><img src="HTTP1.1-requests.png" alt /></p>
<p>You can see that all the requests are made with the <em>HTTP 1.1</em> protocol. Most of today's servers support <em>HTTP 2</em>, which allows multiple requests to be transmitted over one established connection. Fortunately, we now have new native HTTP clients for Flutter, at least for Android and iOS. The best part is that we don't need to change our app's logic at all, except to use the new client implementations. If we switch our app to use the new native clients, our requests will look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719937148735/33d073d0-55ac-4a68-a39b-aff27edb5482.png" alt class="image--center mx-auto" /></p>
<p>At the very top, we see two HTTP 1.1 requests needed to negotiate with the server to use HTTP 2. (Two requests are made because we connect to two different servers.) After that, everything is done using HTTP 2.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719937200241/6f7ef8b0-29e6-4ca4-8c0c-9b9e9c629b2e.png" alt class="image--center mx-auto" /></p>
<p><img src="timing-http2-one-client.png" alt /></p>
<p>It seems that some additional connections are created due to the many image requests, but we can see several orange bars in sequence without any blue. Comparing the total time of all 247 requests to the previous charts, we reduced the loading time by another 20 seconds. This highlights the importance of using the new native clients. All this was recorded on a very fast Wi-Fi connection. Over a cellular connection with higher latencies, this effect will be even more significant.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Setup</td><td>Time for 247 Requests</td></tr>
</thead>
<tbody>
<tr>
<td>DartIo Client, no Client reuse</td><td>77s</td></tr>
<tr>
<td>DartIo Client, one Client for everything</td><td>65,5s</td></tr>
<tr>
<td>cronet Client</td><td>43,7</td></tr>
</tbody>
</table>
</div><p>Surely, the differences will vary depending on the type of requests your app sends, but the gains will be significant.</p>
<p>So clearly, <strong>you should only use one Client</strong> per app unless you need different client settings. You might want a separate client to download large assets with <code>package:cupertino_http</code> so that <code>allowsConstrainedNetworkAccess</code> can be enabled. I also tested using two clients, one for imgix and one for our own server, but it didn't improve the timing; it actually got worse than using just one.</p>
<h2 id="heading-how-to-setup-the-client-for-a-flutter-app-correctly">How to setup the Client for a Flutter App correctly</h2>
<h3 id="heading-the-httpclientfactory">The httpClientFactory</h3>
<p>Let's start with using the new native Http2 clients. For this, we have to add two new packages:</p>
<ul>
<li><p><a target="_blank" href="https://pub.dev/packages/cronet_http">cronet for Android</a></p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/cupertino_http">cupertino_http</a></p>
</li>
</ul>
<p>Then you have to create a platform-specific instance of a Client which is best done by using a factory method:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">const</span> _maxCacheSize = <span class="hljs-number">2</span> * <span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>;

Client httpClientFactory() {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">if</span> (Platform.isAndroid) {
      <span class="hljs-keyword">final</span> engine = CronetEngine.build(
        cacheMode: CacheMode.memory,
        cacheMaxSize: _maxCacheSize,
        enableHttp2: <span class="hljs-keyword">true</span>,
      );
      <span class="hljs-keyword">return</span> CronetClient.fromCronetEngine(engine);
    }

    <span class="hljs-comment">/// <span class="markdown">'You must provide the Content-Length' HTTP header issue</span></span>
    <span class="hljs-keyword">if</span> (Platform.isIOS || Platform.isMacOS) {
      <span class="hljs-keyword">final</span> config = URLSessionConfiguration.ephemeralSessionConfiguration()
        ..cache = URLCache.withCapacity(memoryCapacity: _maxCacheSize);
      <span class="hljs-keyword">return</span> CupertinoClient.fromSessionConfiguration(config);
    }
  } <span class="hljs-keyword">catch</span> (_) {
    <span class="hljs-comment">/// <span class="markdown">in case no Cronet is available which can happen on Android without Google Play Services</span></span>
    <span class="hljs-comment">/// <span class="markdown">not sure if there is a similar case for Cupertino but better safe than sorry</span></span>
    <span class="hljs-keyword">return</span> IOClient(HttpClient());
  }
  <span class="hljs-keyword">final</span> httpClient = HttpClient();
  <span class="hljs-comment">// To use with Fiddler uncomment the following lines and set the</span>
  <span class="hljs-comment">// ip address of the machine where Fiddler is running</span>
  <span class="hljs-comment">// httpClient.findProxy = (uri) =&gt; 'PROXY 192.168.1.61:8866';</span>
  <span class="hljs-comment">// httpClient.badCertificateCallback =</span>
  <span class="hljs-comment">//     (X509Certificate cert, String host, int port) =&gt; true;</span>
  <span class="hljs-keyword">return</span> IOClient(httpClient);
}
</code></pre>
<p>A similar function can be found in the documentation of the <a target="_blank" href="https://pub.dev/packages/http#2-configure-the-http-client"><strong>http</strong></a> package. What isn't mentioned there is that <code>cronet</code> is only available on Android devices with Google Play Services installed. Otherwise, you will encounter this exception:</p>
<pre><code class="lang-dart">java.lang.RuntimeException: All available Cronet providers are disabled. A provider should be enabled before it can be used.
</code></pre>
<p>That's why the above code falls back to normal Dart IO Clients if Cronet is not available.</p>
<blockquote>
<p>in case you want to be sure to have cronet available see this <a target="_blank" href="https://github.com/dart-lang/http/issues/1241#issuecomment-2197755129">discussion on Github</a></p>
</blockquote>
<h3 id="heading-client-type-confusion">Client type confusion</h3>
<p>One thing to note is that <code>CronetClient</code> and <code>CupertinoClient</code> only implement the <code>http.Client</code> interface, not <code>dart:io.HttpClient</code>. This means you might not be able to use these instances in code that expects an <code>HttpClient</code>, like many examples you find online. Usually, it's not a big problem to adapt the code to use <code>http.Client</code> but don't be surprised if the analyzer tells you that your client is not a compatible type.</p>
<p>The reason for this is likely that with the introduction of Flutter-Web, web apps could not access <code>dart:io</code>, where <code>HttpClient</code> is defined. To write code that works across different platforms, they needed to introduce a new common parent interface for HTTP clients, which is <code>http.Client</code>.</p>
<p>So we have the following Client types:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Type</td><td>Package</td><td>compatible with <code>http.Client</code></td><td>platform</td><td>HTTP</td></tr>
</thead>
<tbody>
<tr>
<td>Client</td><td>http</td><td>✔</td><td>abstract interface</td><td></td></tr>
<tr>
<td>CronetClient</td><td>cronet_http</td><td>✔</td><td>Android with Play Services or statically linked</td><td>2</td></tr>
<tr>
<td>CupertinoClient</td><td>cupertino_http</td><td>✔</td><td>iOS/macOS</td><td>2</td></tr>
<tr>
<td>IoClient</td><td>http</td><td>✔ (uses HttpClient internally)</td><td>all platforms but web</td><td>1.1</td></tr>
<tr>
<td>BrowserClient</td><td>http</td><td>✔</td><td>only web</td><td>??</td></tr>
<tr>
<td>FetchClient</td><td>fetch_client</td><td>✔</td><td>only web</td><td>2/3</td></tr>
<tr>
<td>HttpClient</td><td>dart:io</td><td>(can be wrapped in IoClient)</td><td>all but web</td></tr>
</tbody>
</table>
</div><p>Your app should always use the <code>http.Client</code> interface to support all these client types. When creating a new client, use the <code>Client()</code> factory method to ensure the correct implementation is used. If you publish a package, it's best always to give users the option to pass a <code>Client</code> instance as an optional parameter like:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">void</span> myFuncThatNeedsAClient({Client? client}){
    <span class="hljs-keyword">final</span> _client = client ?? Client();
</code></pre>
<blockquote>
<p>Check the docs of <a target="_blank" href="https://pub.dev/documentation/http/latest/browser_client/BrowserClient-class.html">BrowserClient</a> and <a target="_blank" href="https://pub.dev/packages/fetch_client">FetchClient</a> for their specific limitations</p>
</blockquote>
<h4 id="heading-what-about-the-http2-package-you-might-wonder">What about the <code>http2</code> package you might wonder?</h4>
<p>There is an http2 package maintained by the Dart team, which is used as a base for the Dart <code>gRPC</code> package. Unfortunately, it does not implement the <code>http.Client</code> interface, and the documentation is almost non-existent. This <a target="_blank" href="https://stackoverflow.com/a/77378621/1412966">StackOverflow question</a> provides some insight on how to use it. It shouldn't be too difficult to implement <code>http.Client</code> based on this package, so let's hope the Dart team will do that soon. Also if you are using <a target="_blank" href="https://blog.burkharts.net/everything-you-always-wanted-to-know-about-httpclients#heading-using-dio">Dio</a> you can use it with a special adapter.</p>
<h3 id="heading-the-problem-with-runwithclient">The problem with runWithClient</h3>
<p>According to the Dart docs, <a target="_blank" href="https://pub.dev/documentation/http/latest/http/runWithClient.html"><code>runWithClient</code></a> should be the ideal solution to ensure that the app uses the new native clients everywhere. After implementing it, I became suspicious while analyzing the HTTP requests. Despite using <code>runWithClient</code> at the root level of our app, I noticed a large number of HTTP/1.1 requests. Upon closer inspection, all network requests for images from <a target="_blank" href="http://Image.Network"><code>Image.Network</code></a> were still using HTTP/1.1. <a target="_blank" href="https://github.com/flutter/flutter/issues/150941#issue-2378746860">After investigating further</a>, it turns out that the underlying <code>NetworkImage</code> class uses some Flutter internal <code>_HttpClient</code> class and does not call the <code>http.Client()</code> factory constructor. The only alternative is to pass the client instance from <code>httpClientFactory()</code> directly to the parts of our code that need to make a network call.</p>
<p>Another pitfall with <code>runWithClient</code> I lately found out that if you need to create a client inside an isolate and you call <code>Client()</code> it won't call your provided <code>httpClientFactory</code> but use the default <code>dart:io.HttpClient</code>.</p>
<h3 id="heading-registering-the-client">Registering the Client</h3>
<p>Using the service locator or dependency injection of your choice, register one instance at the start of your app. Here, I demonstrate it with <code>get_it</code>:</p>
<pre><code class="lang-dart">  <span class="hljs-keyword">final</span> di = GetIt.instance();
  <span class="hljs-comment">/// <span class="markdown">note that we use additionally <span class="hljs-code">`runWithClient`</span> on the project root, </span></span>
  <span class="hljs-comment">// otherwise we couldn't just call `Client()`</span>
  di.registerSingleton(Client());

  <span class="hljs-comment">/// <span class="markdown">without <span class="hljs-code">`runWithClient`</span></span></span>
  di.registerSingleton&lt;Client&gt;(httpClientFactory());
</code></pre>
<blockquote>
<p>We currently use <code>runWithClient</code> even though we inject the registered Client everywhere with get_it. The main reason is to prevent future packages, which might use the <code>Client()</code> factory method internally, from using the wrong type of Client. If you already inject your Client everywhere, you can probably go without it.</p>
</blockquote>
<h3 id="heading-using-one-client-throughout-the-app">Using one Client throughout the App</h3>
<p>After registration, we can now access our single <code>Client</code> instance via <code>GetIt.I&lt;Client&gt;()</code>.</p>
<blockquote>
<p>If you are using <code>watch_it</code>, you can use <code>di&lt;Client&gt;()</code>.</p>
</blockquote>
<p>Unfortunately, there is no way to set Flutter's internal <code>_sharedClient</code> (I tried <code>debugNetworkImageHttpClientProvider</code>, which is used by <code>NetworkImage</code>, but it expects an <code>http.HttpClient</code> and not an <code>http.Client</code>).</p>
<p>Luckily, there isn't much complexity behind Flutter's <a target="_blank" href="http://Image.network"><code>Image.network</code></a><code>()</code>, which creates an <code>Image</code> widget using an <code>ImageProvider</code> of type <code>NetworkImage</code>.</p>
<blockquote>
<p>The naming here is unfortunate because <code>NetworkImage</code> sounds like a widget but is actually an <code>ImageProvider</code>.</p>
</blockquote>
<p>The easiest solution is to add your own implementation of a <code>NetworkImageProvider</code> to your project by using the original <code>NetworkImage</code> as a blueprint.</p>
<blockquote>
<p>Flutter web uses its own version of NetworkImage. The following version might not be usable with Flutter web or might be less performant.</p>
</blockquote>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HttpNetworkImage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ImageProvider</span>&lt;<span class="hljs-title">HttpNetworkImage</span>&gt; </span>{
  <span class="hljs-comment">/// <span class="markdown">Creates an object that fetches the image at the given URL.</span></span>
  <span class="hljs-keyword">const</span> HttpNetworkImage(<span class="hljs-keyword">this</span>.url, {<span class="hljs-keyword">this</span>.scale = <span class="hljs-number">1.0</span>, <span class="hljs-keyword">this</span>.headers});

  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> url;

  <span class="hljs-keyword">final</span> <span class="hljs-built_in">double</span> scale;

  <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>&gt;? headers;

  <span class="hljs-meta">@override</span>
  Future&lt;HttpNetworkImage&gt; obtainKey(ImageConfiguration configuration) {
    <span class="hljs-keyword">return</span> SynchronousFuture&lt;HttpNetworkImage&gt;(<span class="hljs-keyword">this</span>);
  }

  <span class="hljs-meta">@override</span>
  ImageStreamCompleter loadImage(
      HttpNetworkImage key, ImageDecoderCallback decode) {
    <span class="hljs-comment">// Ownership of this controller is handed off to [_loadAsync]; it is that</span>
    <span class="hljs-comment">// method's responsibility to close the controller's stream when the image</span>
    <span class="hljs-comment">// has been loaded or an error is thrown.</span>

    <span class="hljs-keyword">return</span> MultiFrameImageStreamCompleter(
      codec: _loadAsync(key, decode: decode),
      scale: key.scale,
      debugLabel: key.url,
      informationCollector: () =&gt; &lt;DiagnosticsNode&gt;[
        DiagnosticsProperty&lt;ImageProvider&gt;(<span class="hljs-string">'Image provider'</span>, <span class="hljs-keyword">this</span>),
        DiagnosticsProperty&lt;HttpNetworkImage&gt;(<span class="hljs-string">'Image key'</span>, key),
      ],
    );
  }

  Future&lt;ui.Codec&gt; _loadAsync(
    HttpNetworkImage key, {
    <span class="hljs-keyword">required</span> ImageDecoderCallback decode,
  }) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">assert</span>(key == <span class="hljs-keyword">this</span>);

      <span class="hljs-keyword">final</span> <span class="hljs-built_in">Uri</span> resolved = <span class="hljs-built_in">Uri</span>.base.resolve(key.url);

      <span class="hljs-keyword">final</span> response = <span class="hljs-keyword">await</span>  GetIt.I&lt;Client&gt;().<span class="hljs-keyword">get</span>(resolved, headers: headers);

      <span class="hljs-keyword">if</span> (response.statusCode != HttpStatus.ok) {
        <span class="hljs-comment">// The network may be only temporarily unavailable, or the file will be</span>
        <span class="hljs-comment">// added on the server later. Avoid having future calls to resolve</span>
        <span class="hljs-comment">// fail to check the network again.</span>
        <span class="hljs-comment">// await response.drain&lt;List&lt;int&gt;&gt;(&lt;int&gt;[]);</span>
        <span class="hljs-keyword">throw</span> NetworkImageLoadException(
            statusCode: response.statusCode, uri: resolved);
      }

      <span class="hljs-keyword">if</span> (response.bodyBytes.isEmpty) {
        <span class="hljs-keyword">throw</span> Exception(<span class="hljs-string">'NetworkImage is an empty file: <span class="hljs-subst">$resolved</span>'</span>);
      }

      <span class="hljs-keyword">return</span> decode(<span class="hljs-keyword">await</span> ui.ImmutableBuffer.fromUint8List(response.bodyBytes));
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-comment">// Depending on where the exception was thrown, the image cache may not</span>
      <span class="hljs-comment">// have had a chance to track the key in the cache at all.</span>
      <span class="hljs-comment">// Schedule a microtask to give the cache a chance to add the key.</span>
      scheduleMicrotask(() {
        PaintingBinding.instance.imageCache.evict(key);
      });
      <span class="hljs-keyword">rethrow</span>;
    }
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">bool</span> <span class="hljs-keyword">operator</span> ==(<span class="hljs-built_in">Object</span> other) {
    <span class="hljs-keyword">if</span> (other.runtimeType != runtimeType) {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
    }
    <span class="hljs-keyword">return</span> other <span class="hljs-keyword">is</span> HttpNetworkImage &amp;&amp; other.url == url &amp;&amp; other.scale == scale;
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">int</span> <span class="hljs-keyword">get</span> hashCode =&gt; <span class="hljs-built_in">Object</span>.hash(url, scale);

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">String</span> toString() =&gt;
      <span class="hljs-string">'<span class="hljs-subst">${objectRuntimeType(<span class="hljs-keyword">this</span>, <span class="hljs-string">'HttpNetworkImage'</span>)}</span>("<span class="hljs-subst">$url</span>", scale: <span class="hljs-subst">${scale.toStringAsFixed(<span class="hljs-number">1</span>)}</span>)'</span>;
}
</code></pre>
<blockquote>
<p>If you use <code>provider</code> instead of get_it as your locator you will have to pass the client to this image provider through a parameter.</p>
</blockquote>
<p>Then you can create a network image like:</p>
<pre><code class="lang-dart">Image(image:HttpImage({my_image_url})),
</code></pre>
<blockquote>
<p>If you don't want to have this as part of your code, you can use the package <a target="_blank" href="https://pub.dev/packages/http_image_provider"><code>http_image_provider</code></a>. As we are using <code>get_it</code>, the above solution is more elegant because we don't have to pass the Client to every image, and IMHO it's interesting to show how an ImageProvider works.</p>
</blockquote>
<h3 id="heading-svg-images">SVG Images</h3>
<p>If you are using <code>flutter_svg</code>, you're in luck because <a target="_blank" href="http://SvgPicture.network"><code>SvgPicture.network</code></a> offers a <code>client</code> parameter where you can inject your client instance. It's probably best to create your own widget so that you don't have to pass the client everywhere:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NetworkSvgPicture</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> NetworkSvgPicture(<span class="hljs-keyword">this</span>.url,{<span class="hljs-keyword">super</span>.key});

  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> url;

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> SvgPicture.network(url,httpClient: GetIt.I&lt;Client&gt;());
  }
}
</code></pre>
<h3 id="heading-using-cachednetworkimage">Using <code>cached_network_image</code></h3>
<p>Many of us use the popular and battle-tested <a target="_blank" href="https://pub.dev/packages/cached_network_image">cached_network_image</a> to reduce the number of images our apps need to download on repeated starts. Its API unfortunately doesn't allow passing an HTTP client as a parameter, but you can pass a custom <code>CacheManager</code> instance, which means it's just one level of indirection more. We have to create and register our own <code>CacheManager</code> where we can provide the HTTP client to be used:</p>
<pre><code class="lang-dart">  <span class="hljs-keyword">const</span> cacheKey = <span class="hljs-string">'my_cache'</span>;
  <span class="hljs-keyword">final</span> di = GetIt.instance();
  di.registerSingleton&lt;Client&gt;(httpClientFactory());
  di.registerSingleton&lt;CacheManager&gt;(
    DefaultCacheManager(
      Config(
        cacheKey,
        fileService: HttpFileService(
          httpClient: di&lt;Client&gt;(),
        ),
    stalePeriod: <span class="hljs-keyword">const</span> <span class="hljs-built_in">Duration</span>(days: <span class="hljs-number">7</span>),
    maxNrOfCacheObjects: <span class="hljs-number">200</span>,
      ),
    ),
  );
</code></pre>
<p>Again we create our own image widget</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CachedHtttpNetworkImage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> CachedHtttpNetworkImage(<span class="hljs-keyword">this</span>.url,{<span class="hljs-keyword">super</span>.key});

  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> url;

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> CachedNetworkImage(
        imageUrl: url,
    cacheManager:  GetIt.I&lt;CacheManager&gt;());
  }
}
</code></pre>
<h3 id="heading-bonus-cached-network-svgs">Bonus cached network SVGs</h3>
<p>At first glance, it seems impossible to use <code>cached_network_image</code> with SVG images. Luckily, Dan Field designed <code>flutter_svg</code> to be very flexible and elegant. Instead of an image provider, it uses a <code>ByteLoader</code>, which we can implement to use <code>flutter_cache_manager</code>, the underlying caching engine of <code>cached_network_image</code>.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SvgCachedNetworkLoader</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SvgLoader</span>&lt;<span class="hljs-title">File</span>&gt; </span>{
  <span class="hljs-keyword">const</span> SvgCachedNetworkLoader(
    <span class="hljs-keyword">this</span>.url, {
    <span class="hljs-keyword">this</span>.headers,
    <span class="hljs-keyword">super</span>.theme,
    <span class="hljs-keyword">super</span>.colorMapper,
  });

  <span class="hljs-comment">/// <span class="markdown">The [Uri] encoded resource address.</span></span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> url;

  <span class="hljs-comment">/// <span class="markdown">Optional HTTP headers to send as part of the request.</span></span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>&gt;? headers;

  <span class="hljs-meta">@override</span>
  Future&lt;File&gt; prepareMessage(BuildContext? context) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> GetIt.I&lt;CacheManager&gt;.getSingleFile(url, headers: headers);
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">String</span> provideSvg(File? message) {
    <span class="hljs-keyword">final</span> Uint8List bytes = message!.readAsBytesSync();
    <span class="hljs-keyword">return</span> utf8.decode(bytes, allowMalformed: <span class="hljs-keyword">true</span>);
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">int</span> <span class="hljs-keyword">get</span> hashCode =&gt; <span class="hljs-built_in">Object</span>.hash(url, headers, theme, colorMapper);

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">bool</span> <span class="hljs-keyword">operator</span> ==(<span class="hljs-built_in">Object</span> other) {
    <span class="hljs-keyword">return</span> other <span class="hljs-keyword">is</span> SvgCachedNetworkLoader &amp;&amp;
        other.url == url &amp;&amp;
        other.headers == headers &amp;&amp;
        other.theme == theme &amp;&amp;
        other.colorMapper == colorMapper;
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">String</span> toString() =&gt; <span class="hljs-string">'SvgCachedNetworkLoader(<span class="hljs-subst">$url</span>)'</span>;
}
</code></pre>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CachedNetworkSvgPicture</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> CachedNetworkSvgPicture(<span class="hljs-keyword">this</span>.url, {<span class="hljs-keyword">super</span>.key});

  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> url;

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> SvgPicture(SvgCachedNetworkLoader(url));
  }
}
</code></pre>
<h3 id="heading-using-dio">Using dio</h3>
<p>Many of you use the popular <a target="_blank" href="https://pub.dev/packages/dio">dio</a> package for your HTTP requests. In that case, it is quite easy to use the new native clients because there is a dedicated package called <a target="_blank" href="https://pub.dev/packages/native_dio_adapter"><code>native_dio_adapter</code></a>. Here's how you can do it:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> dioClient = Dio();
dioClient.httpClientAdapter = NativeAdapter();
</code></pre>
<p>Unfortunately, <code>dio</code> doesn't use the <code>http.Client</code> interface internally, likely to avoid a dependency on the <code>http</code> package. However, they do have an adapter concept to connect to any HTTP client you can imagine.</p>
<blockquote>
<p>After the publishing of this article I was told that there is a a special adapter to use the http2 package with Dio which would enable you to get all the benefits of HTTP2 on Windows and Linux. The package is called <a target="_blank" href="https://pub.dev/packages/dio_http2_adapter">dio_http2_adapter</a></p>
</blockquote>
<h2 id="heading-avoiding-pitfalls">Avoiding Pitfalls</h2>
<h3 id="heading-att-multipartrequests-with-cupertinohttp">Att: MultipartRequests with <code>cupertino_http</code></h3>
<p>The current version 1.5.0 of <code>cupertino_http</code> on pub.dev has a bug when sending a <code>MultipartRequest</code>. It doesn't include the expected "Content-Length" header, which causes an error when uploading an image to AWS S3. This issue has been fixed, but you need to use a git reference to the package's GitHub repository until a new version is released on pub.</p>
<h3 id="heading-should-i-close-my-client-at-any-time">Should I close my Client at any time?</h3>
<p>Despite the alarming warning in the API docs of the <code>Client.close()</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719937485279/7c5c3b1e-a17e-418a-ade1-34f9c4879712.png" alt class="image--center mx-auto" /></p>
<p>You should never close the shared instance of your Client. The warning only applies to Dart console apps. When mobile apps terminate, all resources are released automatically.</p>
<h3 id="heading-why-you-should-analyze-your-network-requests">Why You Should Analyze Your Network Requests</h3>
<p>If you don't check your network requests regularly, you might not realize that your app isn't using HTTP2, is making multiple requests when one would suffice, or is downloading more data than necessary because images are in the wrong format. Therefore, I recommend using the Dart Network tool or a debug proxy to see what your app is actually doing.</p>
<h3 id="heading-what-are-these-weird-websocket-requests-in-the-dart-network-tool">What are these weird WebSocket requests in the Dart Network tool?</h3>
<p>When you use the Network Page of the Dart DevTools, you might have wondered about these unexpected requests:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719938632547/7fc41c02-4c97-4449-8cd5-78fc23f72cff.png" alt class="image--center mx-auto" /></p>
<p>It seems that this is a bug in the Network tool because these are not WebSocket requests but actually the connection part of your requests. See <a target="_blank" href="https://github.com/flutter/devtools/issues/3033">this issue</a> on GitHub for more details on why the tool lists them separately. This should definitely be fixed.</p>
<h3 id="heading-needed-preparation-to-use-a-debug-proxy">Needed preparation to use a debug proxy</h3>
<p>If you are using one of the new native HTTP clients, using an external proxy like <em>Fiddler</em> or <em>Charles</em> is now much easier because you can set the proxy settings in the Android or iOS/MacOS network settings. However, if you are using the DartIo Client because you are building on Windows or Linux, or you don't want to use the new native clients, you will find that this Client ignores the OS proxy settings. (This is how I discovered that Flutter NetworkImages were not using the new clients. Only the image requests didn't show up in <em>Fiddler</em>, which made me suspicious).</p>
<p>To use an external debug proxy with DartIo, you have to define the proxy when creating the client:</p>
<pre><code class="lang-dart">  <span class="hljs-keyword">final</span> httpClient = HttpClient();
  httpClient.findProxy = (uri) =&gt; <span class="hljs-string">'PROXY 192.168.1.61:8866'</span>;
  httpClient.badCertificateCallback =
       (X509Certificate cert, <span class="hljs-built_in">String</span> host, <span class="hljs-built_in">int</span> port) =&gt; <span class="hljs-keyword">true</span>;
  <span class="hljs-keyword">return</span> IOClient(httpClient);
</code></pre>
<p>The <code>badCertificateCallback</code> is necessary to prevent DartIo from complaining about the certificate used by the proxy.</p>
<p>If you want to read the content of your requests and you are using SSL/TLS in your app(which you should), you need to install the proxy's certificates on your test device. <em>Fiddler</em> provides an excellent step-by-step guide on how to set up mobile devices. On Android, you need to add a <a target="_blank" href="https://docs.telerik.com/fiddler-everywhere/capture-traffic/capture-from-android#capture-mobile-application-traffic"><code>network_security_config</code></a>.</p>
]]></content:encoded></item><item><title><![CDATA[Let's get this party started]]></title><description><![CDATA[If you haven't used GetIt before you might read this article first: One to find them all. If you are using GetIt for some time make sure to revisit the ReadMe in the API docs because a lot more was added in the last weeks. Like async factories and fa...]]></description><link>https://blog.burkharts.net/lets-get-this-party-started-startup-orchestration-with-getit</link><guid isPermaLink="true">https://blog.burkharts.net/lets-get-this-party-started-startup-orchestration-with-getit</guid><category><![CDATA[uncategorized]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[get_it]]></category><dc:creator><![CDATA[Thomas Burkhart]]></dc:creator><pubDate>Thu, 27 Feb 2020 07:32:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/hTv8aaPziOQ/upload/11f32f4e44c72dfdee5e1be053f87f6f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>If you haven't used <strong>GetIt</strong> before you might read this article first: <a target="_blank" href="http://www.burkharts.net/apps/blog/one-to-find-them-all-how-to-use-service-locators-with-flutter/">One to find them all</a>. If you are using <strong>GetIt</strong> for some time make sure to revisit the <a target="_blank" href="https://pub.dev/documentation/get_it/latest/">ReadMe in the API docs</a> because a lot more was added in the last weeks. Like async factories and factories with parameters.</p>
</blockquote>
<p><strong>To use the features described in this post you need to use</strong> <a target="_blank" href="https://pub.dev/packages/get_it"><strong>get_it V4.0.0</strong></a> <strong>or higher. Please don't be angry with me, it also brings minor breaking changes that improved the API</strong>.</p>
<p>All too often your app has to do a lot of initialization work before it really can get to its actual purpose. And often this includes several asynchronous function calls like:</p>
<ul>
<li><p>Reading from shared_preferences</p>
</li>
<li><p>opening a database or file</p>
</li>
<li><p>Calling some REST API to get the latest data updates</p>
</li>
</ul>
<p>To make things more complicated one or more objects may depend on others to be initialized before they can be initialized like</p>
<ol>
<li><p>Read API token from shared_preferences</p>
</li>
<li><p>before you can make your first REST call</p>
</li>
<li><p>before you can update your database.</p>
</li>
</ol>
<p>You can orchestrate this sequence manually but it's a tedious process. To make your life easier I included some functions in GetIt that do that job for you and integrates nicely in a Flutter project.</p>
<p>Why in <strong>GetIt</strong> and not a separate package you might ask? The reason is that the objects that you register in GetIt are most often the objects that need to get initialized. So it makes sense to combine these processes.</p>
<p>There are two ways you can orchestrate your start-up, one that is almost completely automatic and another one that allows you complete control over the moment when an object signals that it's ready to be used.</p>
<h3 id="heading-so-how-does-it-work">So how does it work</h3>
<p>GetIt offers a function <code>allReady()</code> that completes when all in GetIt registered objects have signaled that they are ready to use.</p>
<pre><code class="lang-dart">Future allReady(Timeout timeout)
</code></pre>
<p>The returned <code>Future</code> is ideally used as the source of a <code>FutureBuilder</code> like:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">return</span> FutureBuilder(
  future: getIt.allReady(),
  builder: (BuildContext context, AsyncSnapshot snapshot) {
    <span class="hljs-keyword">if</span> (snapshot.hasData) {
      <span class="hljs-keyword">return</span> Scaffold(
        body: Center(
          child: Text(<span class="hljs-string">'The first real Page of your App'</span>),
        ),
      );
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> CircularProgressIndicator();
    }
  }
);
</code></pre>
<p>Your app can show some start-up page with an animation and switch the content as soon as <code>allReady()</code> completes.</p>
<p>If you want to switch-out the full page, instead of using a <code>FutureBuilder</code> you can do this in the <code>initState()</code> function of your StatefullWidget:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_StartupPageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    GetIt.I.allReady().then((_) =&gt; Navigator.pushReplacement(
      context,
      MaterialPageRoute(builder: (context) =&gt; MainPage())
    ));
    <span class="hljs-keyword">super</span>.initState();
  }
}
</code></pre>
<h3 id="heading-orchestrating-the-start-up-dance">Orchestrating the start-up dance</h3>
<h4 id="heading-automatic">Automatic</h4>
<p>The easiest way to initialize a Singleton asynchronously is by using the new <code>registerSingletonAsync</code> function, which expects an async factory function. When that function has been completed it notifies <strong>GetIt</strong> that this object is ready. As an example let\'s take this class here:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RestService</span> </span>{
  Future init() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// do your async initialisation...</span>
    <span class="hljs-comment">// simulating it with a Delay here</span>
    <span class="hljs-keyword">await</span> Future.delayed(<span class="hljs-built_in">Duration</span>(seconds: <span class="hljs-number">2</span>));
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>;
  }
}
</code></pre>
<p>All you have to do to make it signal its ready state is to use <code>registerSingletonAsync</code>:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> getIt = GetIt.instance;

getIt.registerSingletonAsync&lt;RestService&gt;(() <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> restService = RestService();
  <span class="hljs-keyword">await</span> restService.init();
  <span class="hljs-keyword">return</span> restService;
});
</code></pre>
<p>If your <code>init</code> function returns its instance like the <code>RestService</code> in the example above you can even write this shorter:</p>
<pre><code class="lang-dart">getIt.registerSingletonAsync(() <span class="hljs-keyword">async</span> =&gt; RestService().init());
</code></pre>
<p>As soon as the last Singleton has finished its factory function the <code>Future</code> from <code>allReady()</code> will complete and trigger your UI to change.</p>
<h4 id="heading-manual">Manual</h4>
<p>You might encounter cases where you need to separate the initialization from the factory function of a Singleton. Or maybe you want to start the initialization as a fire end forget function from the constructor. To still synchronize it with other Singletons you can manually signal that your object is ready by using the <code>signalReady()</code> function. This requires informing <strong>GetIt</strong> that this object will signal ready later so that <strong>GetIt</strong> knows it has to wait for that before completing the <code>allReady() Future</code>. To do this you have two possibilities depending on your preferences:</p>
<ul>
<li><p>Pass the optional <code>signalsReady</code> parameter to the registration functions</p>
</li>
<li><p>Make the type that you register to implement the empty abstract class <code>WillSignalReady</code>. This has the advantage that the one registering the Singleton does not need to know how it will signal its ready state.</p>
</li>
</ul>
<p>Here is an example of the separation of creation and registration:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConfigService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">WillSignalReady</span> </span>{
  Future init() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// do your async initialization...</span>
    GetIt.instance.signalReady(<span class="hljs-keyword">this</span>);
  }
}

<span class="hljs-comment">/// <span class="markdown">registering</span></span>
getIt.registerSingleton(ConfigService());

<span class="hljs-comment">/// <span class="markdown">initializing as a fire and forget async call</span></span>
getIt&lt;ConfigService&gt;().init();
</code></pre>
<p>As you can see we used the non-async registration function in that case because we don't need to. If your factory function needs to be async too, you can use <code>registerSingletonAsync</code> again.</p>
<p>A nice way to hide the whole initialization is to start it as a fire-and-forget-call from the constructor:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConfigService</span> </span>{
  ConfigService() {
    _init();
  }

  Future _init() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// do your async initialisation...</span>
    GetIt.instance.signalReady(<span class="hljs-keyword">this</span>);
  }
}
</code></pre>
<h3 id="heading-dealing-with-dependencies">Dealing with dependencies</h3>
<h4 id="heading-automatic-synchronisation">Automatic synchronisation</h4>
<p>If the singletons that require an async initialization depend on each other we can use the optional parameter <code>dependsOn</code> of <code>registerSingletonAsync</code> and <code>registerSingletonWithDependencies</code>. The latter one is used if you have a Singleton that doesn't need any async initialization but that's constructor depends on other singletons being ready.</p>
<p>Imaging this set of services:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719930904553/543200f3-1534-40eb-8b10-aad09010a905.png" alt="dependencies" /></p>
<p>In code, this could look like this:</p>
<pre><code class="lang-dart">getIt.registerSingletonAsync(() <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> configService = ConfigService();
  <span class="hljs-keyword">await</span> configService.init();
  <span class="hljs-keyword">return</span> configService;
});

getIt.registerSingletonAsync(() <span class="hljs-keyword">async</span> =&gt; RestService().init());

<span class="hljs-comment">/// <span class="markdown">this example uses an async factory function</span></span>
getIt.registerSingletonAsync(createDbServiceAsync, dependsOn: [ConfigService]);

getIt.registerSingletonWithDependencies(
  () =&gt; AppModelImplementation(),
  dependsOn: [ConfigService, DbService, RestService]
);
</code></pre>
<p>This will ensure that dependent singletons will wait with their construction until the ones they depend on have signaled their ready state</p>
<p>Be careful not to create circular dependencies. If you have some deadlocks between the different initialization functions <code>allReady</code> or <code>isReady</code> with throw a <code>WaitingTimeOutException</code> which contains detailed information on who is waiting for whom at that moment.</p>
<h4 id="heading-manual-synchronization">Manual synchronization</h4>
<p>If somehow the automatic synchronization does not fit your need, you can manually wait for another Singleton to signal ready by using the <code>isReadyFunction</code>:</p>
<pre><code class="lang-dart"><span class="hljs-comment">/// <span class="markdown">Returns a Future that completes if the instance of a Singleton, defined by Type [T] or</span></span>
<span class="hljs-comment">/// <span class="markdown">by name [instanceName] or by passing an existing [instance], is ready.</span></span>
<span class="hljs-comment">/// <span class="markdown">If you pass a [timeout], a [WaitingTimeOutException] will be thrown if the instance</span></span>
<span class="hljs-comment">/// <span class="markdown">is not ready in the given time. The Exception contains details on which Singletons are</span></span>
<span class="hljs-comment">/// <span class="markdown">not ready at that time.</span></span>
<span class="hljs-comment">/// <span class="markdown">[callee] optional parameter which makes debugging easier. Pass <span class="hljs-code">`this`</span> in here.</span></span>
Future isReady({
  <span class="hljs-built_in">Object</span> instance,
  <span class="hljs-built_in">String</span> instanceName,
  <span class="hljs-built_in">Duration</span> timeout,
  <span class="hljs-built_in">Object</span> callee,
});
</code></pre>
<p>I hope you like the latest addition to <strong>GetIt</strong> and that it will make your app start-up easier than ever.</p>
]]></content:encoded></item></channel></rss>