<?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[elianortega.dev]]></title><description><![CDATA[I'm a Senior Engineer at Somnio Software, a software development agency. I focus on writing high-quality, scalable, and testable applications. I like to write articles and make videos about tech.]]></description><link>https://elianortega.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 03:00:20 GMT</lastBuildDate><atom:link href="https://elianortega.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[FlutterCon USA 2024 Recap]]></title><description><![CDATA[I wanted to do a quick recap of FlutterCon USA 2024. It was packed with amazing talks, hands-on workshops, and a lot of excitement from the Flutter community. In this post, I’ll share some of my favorite highlights, key takeaways, and a few personal ...]]></description><link>https://elianortega.dev/fluttercon-usa-2024-recap</link><guid isPermaLink="true">https://elianortega.dev/fluttercon-usa-2024-recap</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Fluttercon]]></category><category><![CDATA[FlutterCommunity]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[Dart]]></category><category><![CDATA[backend]]></category><category><![CDATA[offline first]]></category><category><![CDATA[architecture]]></category><category><![CDATA[Serverpod]]></category><category><![CDATA[pub.dev]]></category><category><![CDATA[FlutterconUSA]]></category><dc:creator><![CDATA[Elian Ortega]]></dc:creator><pubDate>Mon, 30 Sep 2024 03:07:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727665094702/8ebbd6bc-6037-4ec5-be34-32fb46dff3db.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I wanted to do a quick recap of FlutterCon USA 2024. It was packed with amazing talks, hands-on workshops, and a lot of excitement from the Flutter community. In this post, I’ll share some of my favorite highlights, key takeaways, and a few personal experiences that made this event special.</p>
<h2 id="heading-flutter-at-10-years">Flutter at 10 years</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727663589184/ea42003c-7a44-4909-863d-ee7db2b0b77f.jpeg" alt class="image--center mx-auto" /></p>
<p>It all began with Eric Seidel's keynote, reflecting on the first 10 years of Flutter. The keynote was a journey through Flutter's evolution, broken down into three key sections:</p>
<h3 id="heading-1-getting-flutter-to-work">1. Getting Flutter to Work</h3>
<p>The first part of the presentation revisited the humble beginnings of Flutter, with Eric recalling one of the early “Sky” demos, which would later evolve into Flutter. You can check out that original demo here: <a target="_blank" href="https://www.youtube.com/watch?v=PnIWl33YMwA">Sky: An Experiment Writing Dart for Mobile (Dart Developer Summit 2015)</a>. It was a powerful reminder of the engineering challenges and achievements that led to what we now recognize as Flutter today. Once those initial demos were up and running, the focus shifted toward building something stable and reliable that developers could use in production.</p>
<h3 id="heading-2-flutter-in-production">2. Flutter in production</h3>
<p>Next, Eric shared how companies began taking a leap of faith with Flutter, with <strong>Hamilton</strong> being the first commercial app developed using the framework, this set the stage for Flutter’s suitability in large-scale production environments. This started a wave of adoption by businesses—big and small—leading to discussions on how to best leverage the framework: from UI and animations to state management and architecture. This laid the foundation for the stable Flutter we know today.</p>
<h3 id="heading-3-flutter-everywhere">3. Flutter Everywhere</h3>
<p>Today, Flutter has over 1 million published apps, and it has been embraced by countless businesses. However, Eric pointed out that there’s still a barrier preventing some large enterprises and legacy businesses from fully adopting Flutter. The need now is for more organic success stories—companies demonstrating Flutter’s power not because of Google partnerships, but because they’ve achieved real success with the framework.</p>
<p>In conclusion, the keynote made it clear that Flutter is ready. As Eric put it, it has become the "safe choice" for multi-platform development. However, there’s still progress to be made on the business front. We now have a stable and reliable framework, but it’s up to both developers and businesses to drive a large adoption of Flutter. And as a final thought, it’s evident that Flutter is here to stay, leading multi-platform development for at least another 10 years.</p>
<h2 id="heading-going-offline-first-with-flutter-by-gianfranco-papa-and-elian-ortega">Going offline first with Flutter by Gianfranco Papa and Elian Ortega</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727663625065/e09ec916-7a8b-4096-a519-f6f9ea595316.png" alt class="image--center mx-auto" /></p>
<p>It was an amazing opportunity to share the stage with Gianfranco Papa at FlutterCon USA for this talk. We wanted to set the foundation for future discussions about offline-first approaches within the Flutter community, a topic we believe is crucial but not widely covered.</p>
<p>The idea behind our talk was to introduce the key concepts of offline-first development, especially in the context of Flutter, where there isn't as much documentation available compared to native Android or iOS. We started by explaining why this approach is essential for modern apps, discussing everything from optimistic UI updates to delta updates and synchronization strategies. We also got into conflict resolution techniques like First Write Wins (FWW), Last Write Wins (LWW), and more advanced strategies like Conflict-Free Replicated Data Types (CRDTs).</p>
<p>Offline-first development isn't trivial. It involves working with local databases, synchronizing data with the server when it's available, and resolving conflicts that arise from multiple devices trying to update the same data. We highlighted how this impacts user experience, making apps feel faster and more responsive while reducing server loads and potentially improving costs. We also stressed that these considerations should be factored into the app's architecture from the start to avoid complications down the line.</p>
<p>We touched on some tools and packages that can help developers implement offline-first functionality in Flutter, including popular local database options like Sqflite, Isar, Hive, and Drift, as well as CRDT solutions for handling more complex synchronization challenges.</p>
<p>Our goal was to set the groundwork for more in-depth talks in the future and encourage developers to consider offline-first strategies early in their projects for a smoother, more efficient development process.</p>
<h2 id="heading-background-processing-with-flutter-and-native-platforms-by-dominik-roszkowski">Background Processing With Flutter and Native Platforms by Dominik Roszkowski</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727663688258/91a3df7b-4c68-4e80-8a75-67291e204fa6.jpeg" alt class="image--center mx-auto" /></p>
<p>I was particularly interested in this talk, as background processing plays a crucial role in creating offline-first applications.</p>
<p>Dominik kicked off the session by highlighting a common Google search: “How to do background processing in Flutter?” The top result typically suggests, “In Flutter, you can just use isolates.” But, as he explained, this is merely the tip of the iceberg.</p>
<p>He then discussed some key considerations when using isolates, such as the fact that they require the Flutter engine and can be invoked either from the main engine or a new engine created in the native code. Dominik also presented a demo showing how to create a new engine and perform background processing using Kotlin for Android.</p>
<p>This led to a bold takeaway: “Maybe write your code natively?” He emphasized that background processing behaviors and APIs vary significantly between iOS and Android. His demo focused on Kotlin and Android, where the environment is more controlled, making it easier to ensure consistent behavior.</p>
<p>Some existing background processing plugins were highlighted, including:</p>
<ul>
<li><p><a target="_blank" href="https://pub.dev/packages/background_fetch">background_fetch</a> by Transistor Software</p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/workmanager">workmanager</a> by Flutter Community</p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/flutter_background_service">flutter_background_service</a> by ekasetiawans</p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/flutter_downloader">flutter_downloader</a>, <a target="_blank" href="https://pub.dev/packages/background_downloader">background_downloader</a>, and <a target="_blank" href="https://pub.dev/packages/flutter_uploader">flutter_uploader</a></p>
</li>
</ul>
<p>It was an excellent talk, and I highly recommend watching it if you’re planning to work on any background processing tasks. You can catch the talk recorded during FlutterCon Europe <a target="_blank" href="https://www.droidcon.com/2024/09/03/native-background-processing-with-flutter/">here</a>.</p>
<h2 id="heading-stop-centralizing-a-humble-approach-to-flutter-app-architecture-by-matt-carroll">Stop centralizing! A humble approach to Flutter app architecture by Matt Carroll</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727663711655/b6b44880-2aed-47ff-ba67-9af11b4cdd74.jpeg" alt class="image--center mx-auto" /></p>
<p>I’m a big fan of architecture, and I always find it valuable to explore new approaches.</p>
<p>Matt kicked off by discussing the pros and cons of centralizing app components and features—things like UI themes and internationalization. But the real focus came when he addressed <strong>navigation</strong> through the example of a multistep form for buying movie tickets.</p>
<p>In this example, Matt demonstrated how a centralized navigation approach could introduce unnecessary complexity for a relatively simple feature. Instead, he proposed disconnecting the multistep form from centralized navigation and managing the flow using a <code>StatefulWidget</code> and a <code>switch</code> statement to display different widgets based on the current step which was handled with an <code>enum</code>. Naturally, this raised concerns like, “What if I need to go back?” and “What about navigation transitions?” The solution was simple: add an <code>onBackRequested</code> callback to each view for handling back navigation, and use a custom widget (I believe it was called <code>StackNavigation</code>) to preserve state and animate between steps.</p>
<p>While this approach might be controversial for some developers, the key takeaway was clear: in many cases, opting for a simpler architecture can lead to the same results, but with faster development and less complexity.</p>
<h2 id="heading-the-power-of-macros-in-dart-by-gianfranco-papa">The power of Macros in Dart by Gianfranco Papa</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727663760119/00d2186a-2596-4ed4-bbf3-6e4d9f2f98dc.png" alt class="image--center mx-auto" /></p>
<p>Macros are one of the most anticipated features in Dart, offering the potential to introduce powerful metaprogramming into the language. Gianfranco's talk was all about breaking down what macros are, how they can be used in day-to-day coding, and how developers can create their own macros to make life easier.</p>
<p>He also went into the technical side of things, talking about the different stages of a macro’s lifecycle and explaining how the order of execution works behind the scenes. This gave everyone a better understanding of how macros will function in practice.</p>
<p>Gianfranco wrapped up by highlighting how macros could change the way we write Dart code—especially by reducing the need for code generation tools, like those used for JSON serialization. This shift could lead to cleaner, more efficient codebases with fewer workarounds. It’s clear that macros are going to make a big impact when they officially land in Dart.</p>
<h2 id="heading-from-frontend-to-backend-with-serverpod-by-viktor-lidholt">From frontend to backend with Serverpod by Viktor Lidholt</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727663787525/0a169f54-5ca3-4575-a800-7ec977bba85c.jpeg" alt class="image--center mx-auto" /></p>
<p>I was particularly interested in this talk because I’ve been working with Dart on the server almost daily for the past few months. Naturally, I’d heard of Serverpod but hadn’t taken the time to dive deeper into it.</p>
<p>With my current Dart-on-the-server setup, I’ve faced several challenges. For instance, logging has been tricky to implement, especially when trying to make the logs look good in GCP. I’ve also had issues with health checks to ensure the API is running smoothly, and I ended up handling authentication in the mobile app using Firebase for simplicity. Since I’m keen to explore more of the backend world (I’ve worked with NestJS for some projects), I had this feeling that my current setup was missing a lot.</p>
<p>That’s why, when I saw the second slide of the talk listing features Serverpod has built-in, I was immediately intrigued. The list included:</p>
<ul>
<li><p>Logging (with a companion app to view logs)</p>
</li>
<li><p>File uploads</p>
</li>
<li><p>Authentication with major providers (Email, Google, Apple, Firebase)</p>
</li>
<li><p>Data streaming</p>
</li>
<li><p>Task scheduling</p>
</li>
<li><p>Health checks</p>
</li>
<li><p>Easy deployment</p>
</li>
<li><p>Data migrations</p>
</li>
</ul>
<p>Knowing that Serverpod is so feature-rich and backed by a team of 8 developers dedicated to its growth gave me a lot of confidence in its potential for long-term support and new features.</p>
<p>Viktor then went on to demo how to create models, run migrations, and build an endpoint. The process felt very similar to my current workflow, but with a lot more built-in functionality and benefits.</p>
<p>I’m genuinely excited to try Serverpod myself and might even write a blog post about my experience. If you're curious to learn more, check out their excellent documentation at <a target="_blank" href="https://serverpod.dev/">serverpod.dev</a>.</p>
<h2 id="heading-building-a-package-with-assets-by-moritz-summermann-and-daco-harkes">Building a package with assets by Moritz Sümmermann and Daco Harkes</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727663811000/247efd5e-3068-4d68-8f05-189321a4d193.jpeg" alt class="image--center mx-auto" /></p>
<p>This talk grabbed my attention because it featured two Googlers from the Dart compiler team. In my view, the more Dart advances as a language, the more Flutter will benefit as a framework for cross-platform development.</p>
<p>The session began with a disclaimer: some of the demos shown were only available on the Flutter master channel behind experimental flags, and a few only worked on their laptops (which was very funny 😂). Moritz and Daco then showcased an <code>add</code> and <code>subtract</code> function written in C, which was built and bundled as part of a Dart package asset. They also demonstrated how tree-shaking applies to native code—if the <code>subtract</code> function wasn’t used in the Dart code, it was excluded from the final native build. This technique worked for other assets too, such as JSON files.</p>
<p>As they walked through the process, they explained the hooks involved in building and linking these native code assets. They emphasized that their implementation followed protocols that would allow support for other programming languages, such as Rust, Kotlin, and Swift, in the future.</p>
<p>The end goal of the "Asset" feature in Dart and Flutter is to facilitate the use of pre-existing code from other languages, allowing developers to integrate it seamlessly within their Dart projects.</p>
<h2 id="heading-future-of-dart-panel">Future of Dart Panel</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727663838028/e8365a0d-71d0-4aed-9e09-72c2294fba15.png" alt class="image--center mx-auto" /></p>
<p>This panel was one of my favorite events at FlutterCon, offering valuable insights while engaging with questions directly from the community. The open format allowed attendees to hear the thoughts and opinions of the Dart team, and there were some particularly interesting discussions.</p>
<p>One recurring question, reflecting concerns within the developer community, was: “Is Google going to kill Dart and Flutter?” Michael Thomsen’s response was clear—this question has been asked repeatedly since the begining of the language, and Dart is still going strong. His response gave a sense of confidence for the future of both Dart and Flutter. They also mentioned appreciation for community contributions to the Dart ecosystem.</p>
<p>Other questions ranged from thoughts on WebAssembly (WASM) for Flutter Web to things the team might have done differently. For example, there was once an intention to remove semicolons from the language, but this was ultimately scrapped due to the enormous impact it would have had on the framework's core—simply too disruptive to consider.</p>
<p>The main takeaway for me was the reiterated message that Dart and Flutter are here to stay, with the team’s main focus now on highly anticipated features like WASM and macros—features that the community has been asking for quite a bit.</p>
<h2 id="heading-flutter-and-dart-package-ecosystem-summit">Flutter and Dart Package Ecosystem Summit</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727663892056/e6de00b8-0204-43e2-a5f8-71869b120097.png" alt class="image--center mx-auto" /></p>
<p>The "Flutter and Dart Package Ecosystem Summit" was another great session I attended, focusing on the current state and future of the package ecosystem in Flutter and Dart. This session aimed to address some of the key issues and challenges developers face, such as abandoned packages and finding ways to encourage ongoing maintenance through potential compensation models for package maintainers.</p>
<p>The discussion was led by Ander Dobo and Rody Davis, with members of the Dart team, package creators, and community members actively participating. It was great to see that everyone involved is committed to avoiding the pitfalls that have impacted other open-source ecosystems in the past.</p>
<p>Some of the key takeaways for me were:</p>
<ul>
<li><p><strong>Support for Package Maintainers</strong>: There’s clear interest from the community in finding ways to support package maintainers, possibly through a compensation model. While nothing has been stablished, starting this conversation is a step in the right direction to ensure long-term package maintenance.</p>
</li>
<li><p><strong>Flutter Community GitHub</strong>: If you can no longer maintain your package and want to prevent it from becoming obsolete, you can transfer it to the <a target="_blank" href="https://github.com/fluttercommunity/community">FlutterCommunity GitHub</a>, where it will continue to be maintained by the community. You will still lead the package development but it will be backed up by the Flutter Community team.</p>
</li>
<li><p><strong>Pub.dev Package Name Recovery</strong>: In cases where a package has been abandoned for a significant period, developers can reach out to <a target="_blank" href="http://pub.dev/">pub.dev</a> to request that the package name be made available again.</p>
</li>
<li><p><strong>Namespace Discussions</strong>: There’s growing interest in introducing namespaces for packages to avoid conflicts where two packages share the same name. This could be an important change to ensure smoother package management as the ecosystem continues to grow.</p>
</li>
</ul>
<p>This session highlighted how much thought is being put into ensuring that the Flutter and Dart package ecosystem remains sustainable and avoids common issues seen in other communities.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>There were so many other amazing talks and sessions I couldn’t attend live, but I’m definitely looking forward to watching the recordings once they’re available. If possible, I might even write another post to highlight some of my favorite sessions that I couldn't catch.</p>
<p>All in all, FlutterCon USA was an incredible experience! It was amazing to meet so many talented people from the Flutter community in person, share knowledge, and be part of such a supportive network. Huge thanks to my team at Somnio Software, especially Mauricio Pastorini and Gianfranco Papa, for all their support and collaboration. I'm already excited for the next FlutterCon and to continue being part of this fantastic community.</p>
<p>What a great experience! See you all at the next one!</p>
]]></content:encoded></item><item><title><![CDATA[Cleaner Flutter Vol. 5: Establishing use cases]]></title><description><![CDATA[💡 Note: This article is part of the Cleaner Flutter series by my friend Marcos Sevilla to which I contribute. You can find the complete series here.

Hello and welcome to the last volume where we will talk about the domain layer of our Clean Archite...]]></description><link>https://elianortega.dev/cleaner-flutter-vol-5-establishing-use-cases</link><guid isPermaLink="true">https://elianortega.dev/cleaner-flutter-vol-5-establishing-use-cases</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[clean code]]></category><dc:creator><![CDATA[Elian Ortega]]></dc:creator><pubDate>Sun, 27 Feb 2022 15:03:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645973488832/ro0WuFHzv.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>💡 Note: This article is part of the Cleaner Flutter series by my friend <a target="_blank" href="https://marcossevilla.dev">Marcos Sevilla</a> to which I contribute. You can find the complete series <a target="_blank" href="https://marcossevilla.dev/series/cleaner-flutter">here</a>.</p>
</blockquote>
<p>Hello and welcome to the last volume where we will talk about the domain layer of our Clean Architecture proposal. Last time we talked about a fundamental part: the repositories, which allow us to create the communication of our software with the outside world.</p>
<p>On this occasion, with the use cases we are going to achieve independence from the specific interactions of the user with the system, using use cases and thus obtain maintainable, reusable code that truly meets the needs of the business.</p>
<p>But before we get into technical matters, we must remember that there are 2 perspectives: a developer's and a business perspective of the product. This differentiation is a very important topic since by being clear about the objectives of the product we can develop software in a better way.</p>
<p>Remember that all software products should first go through a process where their requirements and the interactions of a user with the software product are defined, whether this is a <code>login</code>, <code>addToShoppingCart</code>, or <code>processPayment</code>.</p>
<p>Ok, let's get into it.</p>
<h1 id="heading-what-are-use-cases">What are use cases?</h1>
<p>In Uncle Bob's words...</p>
<blockquote>
<p>The software in this layer contains the application-specific business rules. These encapsulate and implement all the use cases of the system.</p>
</blockquote>
<p>I remind you that the use cases mentioned in the definition do not refer coding part or module, but to the use cases that came from de planning phase of the project.</p>
<p>Each event is an interaction of the user with the system and we can call this a use case. As this is also a business concept the most common way of displaying the use cases of the system is by the use case graph, which looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645973579003/P_PVgVQdP.png" alt="0_D5fgzZp_5AeUFkKP.png" /></p>
<p>Taken from <a target="_blank" href="https://warren2lynch.medium.com/use-case-tutorial-for-dummies-8cf426043710">Use Case Tutorial for Dummies</a>.</p>
<p>Here we have objects like:</p>
<ul>
<li><code>Actor</code>: Users who interact with the system (might be another software too).</li>
<li><code>UseCase</code>: How the actor uses the system to fulfill some functionality.</li>
<li><code>Relationship</code>: The relationship between the actors and the use cases.</li>
</ul>
<p>The example shows some use cases for a passenger at an airport. Which, as I mentioned before, is the user's interactions with the system, in this case, the logic of an airport.</p>
<h1 id="heading-in-code">In code</h1>
<p>In my experience, I have noticed that the use case layer is usually bypassed and replaced using methods directly in the software's business logic. But when we implement it we achieve better decoupling and other advantages.</p>
<p>By adding the use cases layer the folder structure for the domain layer would look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645973723941/zd7wjFSEc.jpeg" alt="gUDaTXF2V.jpeg" /></p>
<h1 id="heading-complying-with-the-principles">Complying with the principles</h1>
<p><strong>They end up being simpler repositories</strong>. When we create the use cases, we manage to facilitate communication with the repositories and we make sure to have better abstractions that meet the specific objective of that use case, and as we saw in previous articles, with this we comply with the single responsibility principle.</p>
<p><strong>We use interfaces, not implementations</strong>. In use cases we are normally going to depend on a repository, which rather must be an interface, this way we would be complying with the Liskov substitution principle, since we could replace the repository with any other that implements the same interface and this will not affect the base logic of our use case.</p>
<h1 id="heading-implementing-in-dart">Implementing in Dart</h1>
<p>Like any of the components we've talked about throughout the series, each can be implemented in many ways. This time with the use cases we are going to use one of Dart's not very well-known features.</p>
<p>In Dart, there is something called callable classes. This allows us to use the instance of a class as if it were a function. To achieve this, all we have to do is implement the <code>call</code> method inside the class.</p>
<p>We get something like this:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CallableClass</span> </span>{
  <span class="hljs-built_in">String</span> call() =&gt; <span class="hljs-string">'Thanks for calling!'</span>;
}

<span class="hljs-keyword">void</span> main() {
  <span class="hljs-keyword">final</span> callable = CallableClass();
  <span class="hljs-built_in">print</span>(callable()); <span class="hljs-comment">// Thanks for calling!</span>
}
</code></pre>
<p>Using this Dart feature, we can create a class for each of our use cases and implement the call method with the respective functionality.</p>
<h1 id="heading-putting-the-pieces-together">Putting the pieces together</h1>
<p>With all the information we have, we can already see an example with all the pieces that we have carried so far: entities, repositories, and use cases.</p>
<p>The code is simplified to facilitate the example. I also remind you that to understand the examples you must read the previous articles. <a target="_blank" href="https://marcossevilla.dev/cleaner-flutter-vol-4">Here's</a> the previous one.</p>
<p>Let's imagine a very simple case of a <code>signIn</code>, for this, we are going to have a user entity and a repository interface that contains a method to make the<code>signIn</code> using an email and password.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{
  <span class="hljs-keyword">const</span> User({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.email,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.password,
  });

  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> email;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> password;
}

<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthRepositoryInterface</span> </span>{
  Future&lt;User&gt; signIn({
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> email,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> password,
  });
}
</code></pre>
<p>This is where many developers decide not to implement the use case layer, so they call the repositories directly from the application's business logic.</p>
<p>But since we are using use cases, we create a <code>SignInUser</code> class which will be our use case. This will have a dependency on the <code>AuthRepositoryInterface</code>, then we implement the <code>call</code> method and call <code>signIn</code>. And so we have a use case with sole responsibility and decouple the repositories from the business logic (state manager).</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserSignIn</span> </span>{
  <span class="hljs-keyword">const</span> UserSignIn({
    <span class="hljs-keyword">required</span> AuthRepositoryInterface authRepository,
  }) : _authRepository = authRepository;

  <span class="hljs-keyword">final</span> AuthRepositoryInterface _authRepository;

  Future&lt;User&gt; call({
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> email,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> password,
  }) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> _authRepository.signIn(
      email: email,
      password: password,
    );
  }
}
</code></pre>
<blockquote>
<p>It doesn't matter who wins the endless battle of which is the best state management tool, since it does not matter which one we select. If we even decide to change, this does not affect our implementation at all.</p>
</blockquote>
<p>This example is quite simple, but for the moment what we are trying to do is understand each of the components that are part of this Clean Architecture proposal. Later we will make examples and videos applying all the theories with production code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645974121406/SWUpAqS-d.gif" alt="giphy (1).gif" /></p>
<h1 id="heading-continue-with-the-cleaner-flutter-series">Continue with the Cleaner Flutter Series</h1>
<p>You can go back to the series list <a target="_blank" href="https://marcossevilla.dev/series/cleaner-flutter">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[FutureProvider: how to use it and how to test it.]]></title><description><![CDATA[We can't talk about Flutter state management solutions without talking about package:riverpod.
Of course, just as I have mentioned in other articles, in my opinion, package:riverpod is more of a dependency injection tool than a state management solut...]]></description><link>https://elianortega.dev/futureprovider-how-to-use-it-and-how-to-test-it</link><guid isPermaLink="true">https://elianortega.dev/futureprovider-how-to-use-it-and-how-to-test-it</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[General Programming]]></category><category><![CDATA[Flutter Examples]]></category><dc:creator><![CDATA[Elian Ortega]]></dc:creator><pubDate>Sat, 26 Feb 2022 15:44:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645890159832/jjSYf3So3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We can't talk about Flutter state management solutions without talking about <a target="_blank" href="https://riverpod.dev/"><code>package:riverpod</code></a>.</p>
<p>Of course, just as I have mentioned in other articles, in my opinion, <code>package:riverpod</code> is more of a dependency injection tool than a state management solution. It does include APIs like <code>StateNotifier</code> which can be used as a state management solution but that is a topic for another article. </p>
<p>Today I want to focus on one of the tools that are included inside the <code>package:riverpod</code>. The <code>FutureProvider</code>.</p>
<h1 id="heading-the-problem">The problem</h1>
<p>Almost every app has one feature that makes a single asynchronous operation to get some data that later on will be displayed to the user using some widgets.</p>
<p>As a good developer, you create a new feature folder with the <code>view/</code> and your selected state management solution call it <code>bloc/</code>, <code>cubit/</code>, <code>state_notifier/</code>, <code>provider/</code> or whatever you want. </p>
<p>But while writing every single line of code you are thinking: </p>
<blockquote>
<p>It's kind of non-sense to write this amount of code for an extremely simple feature.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645159678765/izCfMkrl9.gif" alt="giphy" /></p>
<h1 id="heading-enters-futureprovider">Enters FutureProvider</h1>
<p>Straight from the <a target="_blank" href="https://pub.dev/documentation/riverpod/latest/riverpod/FutureProvider-class.html">docs</a>:</p>
<blockquote>
<p>A <code>FutureProvider</code> can be considered as a combination of Provider and FutureBuilder. By using <code>FutureProvider</code>, the UI will be able to read the state of the future synchronously, handle the loading/error states, and rebuild when the future completes.</p>
</blockquote>
<p>We get access to <strong>data</strong>, <strong>loading</strong>, and <strong>error</strong> states based on a <code>Future</code> operation that is usually an http request. </p>
<p>It's important to understand that we get access to these states thanks to <a target="_blank" href="https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html"><code>AsyncValue</code></a> that is another API included in the <code>package:riverpod</code>.</p>
<h1 id="heading-the-code">The code</h1>
<p>Nothing is better than the code itself to understand how a <code>FutureProvider</code> works.</p>
<p>You are going to build the following app:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645161327685/7pdHMpm1I.gif" alt="jokes_app_demo.gif" />
It's a simple app that fetches a new programming joke after pressing a button. It uses the following <a target="_blank" href="https://sv443.net/jokeapi/v2/">jokes API sv443</a>.</p>
<h2 id="heading-folderfile-structure">Folder/File structure</h2>
<p>To start, we are going to create the following folder/file structure.</p>
<pre><code>.
└── lib<span class="hljs-operator">/</span>
    ├── app<span class="hljs-operator">/</span>
    │   └── app.dart
    └── jokes<span class="hljs-operator">/</span>
        ├── logic<span class="hljs-operator">/</span>
        │   └── jokes_provider.dart
        ├── model<span class="hljs-operator">/</span>
        │   ├── joke.dart
        │   └── models.dart (barrel file)
        ├── <span class="hljs-keyword">view</span><span class="hljs-operator">/</span>
        │   └── jokes_page.dart
        └── jokes.dart (barrel file)
</code></pre><p>The app can be generated with the default <code>flutter create</code> command. It is going to have a single feature <code>jokes/</code>, separated into 3 parts. First, the logic of the feature in the <code>/logic</code> folder, which is going to contain the <code>FutureProvider</code> inside <code>jokes_provider.dart</code>. Then the user interfaces in the <code>/view</code> folder, which is going to contain the <code>JokesPage</code> inside <code>jokes_page.dart</code>. Finally, we need a model to serialize the response from the API so we are going to create a <code>Joke</code> model in the<code>model/</code> folder inside <code>joke.dart</code>.</p>
<h2 id="heading-joke-model">Joke Model</h2>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:equatable/equatable.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Joke</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Equatable</span> </span>{
  <span class="hljs-keyword">const</span> Joke({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.error,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.category,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.type,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.setup,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.delivery,
    <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>.safe,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.lang,
  });

  <span class="hljs-keyword">factory</span> Joke.fromJson(<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; json) =&gt; Joke(
        error: json[<span class="hljs-string">'error'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">bool?</span> ?? <span class="hljs-keyword">false</span>,
        category: json[<span class="hljs-string">'category'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">String?</span> ?? <span class="hljs-string">''</span>,
        type: json[<span class="hljs-string">'type'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">String?</span> ?? <span class="hljs-string">''</span>,
        setup: json[<span class="hljs-string">'setup'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">String?</span> ?? <span class="hljs-string">''</span>,
        delivery: json[<span class="hljs-string">'delivery'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">String?</span> ?? <span class="hljs-string">''</span>,
        id: (json[<span class="hljs-string">'id'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">num?</span>)?.toInt() ?? <span class="hljs-number">0</span>,
        safe: json[<span class="hljs-string">'safe'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">bool?</span> ?? <span class="hljs-keyword">false</span>,
        lang: json[<span class="hljs-string">'lang'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">String?</span> ?? <span class="hljs-string">''</span>,
      );

  <span class="hljs-keyword">final</span> <span class="hljs-built_in">bool</span> error;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> category;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> type;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> setup;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> delivery;
  <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">bool</span> safe;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> lang;

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Object?</span>&gt; <span class="hljs-keyword">get</span> props =&gt; [
        error,
        category,
        type,
        setup,
        delivery,
        id,
        safe,
        lang,
      ];
}
</code></pre>
<p>This class will be in charge of serializing the JSON response from the API. You can check a sample response <a target="_blank" href="https://v2.jokeapi.dev/joke/Programming?type=twopart">here</a>.</p>
<h2 id="heading-jokes-logic">Jokes Logic</h2>
<p>Now, the reason we are here the <code>FutureProvider</code> that will make the HTTP request and handle the states changes of the joke query. </p>
<p>For this example, I'm using <a target="_blank" href="https://pub.dev/packages/dio"><strong>Dio</strong></a> as the http client.</p>
<h3 id="heading-dio-client-provider">Dio Client Provider</h3>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> dioClientProvider = Provider&lt;Dio&gt;(
  (ref) =&gt; Dio(
    BaseOptions(baseUrl: <span class="hljs-string">'https://v2.jokeapi.dev/joke'</span>),
  ),
);
</code></pre>
<p>It's important to create an independent <code>Provider</code> for the http client so that later we can mock it for testing purposes of the <code>FutureProvider</code>. </p>
<h3 id="heading-jokes-future-provider">Jokes Future Provider</h3>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> jokesFutureProvider = FutureProvider&lt;Joke&gt;((ref) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> result = <span class="hljs-keyword">await</span> ref.read(dioClientProvider).<span class="hljs-keyword">get</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;(
        <span class="hljs-string">'/Programming?type=twopart'</span>,
      );

  <span class="hljs-keyword">return</span> Joke.fromJson(result.data!);
});
</code></pre>
<p>Yes... that's it. </p>
<p>With these lines of code, we're able to handle the loading, data, and error states of the request. </p>
<p>When called, the <code>jokesFutureProvider</code> will make the http request. On success, it will update its state to <code>AsyncValue.data(Joke)</code>. If any errors occur during the request or serialization, the error will be automatically caught and the state will be updated to  <code>AsyncValue.error(ErrorObject)</code>. And while loading the <code>jokesFutureProvider</code> state will be <code>AsyncValue.loading</code>.</p>
<h2 id="heading-jokes-page">Jokes Page</h2>
<p>Now we just need to create a User Interface to see the <code>jokesFutureProvider</code> in action. </p>
<h3 id="heading-jokewidget">JokeWidget</h3>
<p>First, we can  subscribe to state changes of the <code>jokesFutureProvider</code> doing:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> jokeState = ref.watch(jokesFutureProvider);
</code></pre>
<blockquote>
<p>🚀 calling <code>refwatch</code> on the <code>jokesFutureProvider</code> will trigger the asynchronous operation of the <code>FutureProvider</code>.</p>
</blockquote>
<p>The jokeState variable will give you access to the AsyncValue object and with its built-in <code>when</code> method we can easily handle the different states of the <code>FutureProvider</code>.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JokeWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ConsumerWidget</span> </span>{
  <span class="hljs-keyword">const</span> JokeWidget({Key? key}) : <span class="hljs-keyword">super</span>(key: key);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context, WidgetRef ref) {
    <span class="hljs-keyword">final</span> jokeState = ref.watch(jokesFutureProvider);

    <span class="hljs-keyword">return</span> jokeState.when(
      loading: () =&gt; <span class="hljs-keyword">const</span> Center(child: CircularProgressIndicator()),
      error: (error, _) =&gt; <span class="hljs-keyword">const</span> Text(
        <span class="hljs-string">'Ups an error occurred. Try again later.'</span>,
        key: <span class="hljs-keyword">const</span> Key(<span class="hljs-string">'jokesPage_jokeWidget_errorText'</span>),
      ),
      data: (joke) =&gt; Container(
        key: <span class="hljs-keyword">const</span> Key(<span class="hljs-string">'jokesPage_jokeWidget_jokeContainer'</span>),
        padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">20</span>),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(<span class="hljs-number">20</span>),
          color: Theme.of(context).secondaryHeaderColor,
        ),
        child: Column(
          children: [
            Text(joke.setup),
            Text(joke.delivery),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<h3 id="heading-get-joke-button">Get Joke Button</h3>
<p>We have a widget that shows the joke, but we need a button to be able to refresh this joke and get a new one. </p>
<p>Calling <code>ref.refresh(jokesFutureProvider)</code> will refresh the provider and repeat the asynchronous operation. </p>
<pre><code class="lang-dart">Consumer(
  builder: (context, ref, _) {
    <span class="hljs-keyword">return</span> Align(
      child: ElevatedButton(
        onPressed: () {
          ref.refresh(jokesFutureProvider);
        },
        child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Get new joke'</span>),
      ),
    );
  },
),
</code></pre>
<p>Finally, we can put all of it together in the <code>JokesPage</code>:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_riverpod/flutter_riverpod.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:future_provider_jokes_app/jokes/jokes.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JokesPage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-function"><span class="hljs-keyword">const</span> <span class="hljs-title">JokesPage</span><span class="hljs-params">({Key? key})</span> : <span class="hljs-title">super</span><span class="hljs-params">(key: key)</span></span>;

  <span class="hljs-meta">@override</span>
  <span class="hljs-function">Widget <span class="hljs-title">build</span><span class="hljs-params">(BuildContext context)</span> </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">const</span> <span class="hljs-title">JokesView</span><span class="hljs-params">()</span></span>;
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JokesView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-function"><span class="hljs-keyword">const</span> <span class="hljs-title">JokesView</span><span class="hljs-params">({Key? key})</span> : <span class="hljs-title">super</span><span class="hljs-params">(key: key)</span></span>;

  <span class="hljs-meta">@override</span>
  <span class="hljs-function">Widget <span class="hljs-title">build</span><span class="hljs-params">(BuildContext context)</span> </span>{
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(title: <span class="hljs-function"><span class="hljs-keyword">const</span> <span class="hljs-title">Text</span><span class="hljs-params">(<span class="hljs-string">'JokesApp'</span>)</span>),
      body: <span class="hljs-title">Padding</span><span class="hljs-params">(
        padding: <span class="hljs-keyword">const</span> EdgeInsets.symmetric(horizontal: <span class="hljs-number">20</span>)</span>,
        child: <span class="hljs-title">Column</span><span class="hljs-params">(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            <span class="hljs-keyword">const</span> JokeWidget()</span>,
            <span class="hljs-keyword">const</span> <span class="hljs-title">SizedBox</span><span class="hljs-params">(height: <span class="hljs-number">10</span>)</span>,
            <span class="hljs-title">Consumer</span><span class="hljs-params">(
              builder: (context, ref, _)</span> </span>{
                <span class="hljs-keyword">return</span> Align(
                  child: ElevatedButton(
                    onPressed: () {
                      ref.refresh(jokesFutureProvider);
                    },
                    child: <span class="hljs-function"><span class="hljs-keyword">const</span> <span class="hljs-title">Text</span><span class="hljs-params">(<span class="hljs-string">'Get new joke'</span>)</span>,
                  ),
                )</span>;
              },
            ),
          ],
        ),
      ),
    );
  }
}
</code></pre><p>With this, we have a fully implemented feature that gets us funny programming jokes 🎉.</p>
<p>Just in case you need it, here you have the other files needed to run the app.</p>
<h2 id="heading-other-files">Other files</h2>
<h3 id="heading-appdart">app.dart</h3>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:future_provider_jokes_app/jokes/jokes.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JokesApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> JokesApp({Key? key}) : <span class="hljs-keyword">super</span>(key: key);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      theme: ThemeData.dark(),
      home: <span class="hljs-keyword">const</span> JokesPage(),
    );
  }
}
</code></pre>
<p>With this we have </p>
<h3 id="heading-maindart">main.dart</h3>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_riverpod/flutter_riverpod.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:future_provider_jokes_app/app/app.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  runApp(<span class="hljs-keyword">const</span> ProviderScope(child: JokesApp()));
}
</code></pre>
<h1 id="heading-testing">Testing 🧪</h1>
<p>We cannot call ourselves good developers if we do not test our code. In this channel we test everything.</p>
<p>Tests folder structure:</p>
<pre><code>test<span class="hljs-operator">/</span>
└── jokes<span class="hljs-operator">/</span>
    ├── logic<span class="hljs-operator">/</span>
    │   └── jokes_provider_test.dart
    └── <span class="hljs-keyword">view</span><span class="hljs-operator">/</span>
        └── jokes_page_test.dart
</code></pre><p>Let's start with the logic of our app. </p>
<blockquote>
<p>For the tests I'll assume you have some extra knowledge of how <code>package:riverpod</code> works. If you would like a more in detailed article on <code>Override</code> and <code>ProviderContainer</code> please let me know in the comments.</p>
</blockquote>
<h2 id="heading-dioprovider-test">DioProvider Test</h2>
<p>We can add a test to validate that the <code>Dio</code> client has the correct url. </p>
<pre><code class="lang-dart"><span class="hljs-keyword">void</span> main() {
  group(<span class="hljs-string">'dioClientProvider'</span>, () {
    test(<span class="hljs-string">'is a Dio with https://v2.jokeapi.dev/joke base url'</span>, () {
      <span class="hljs-keyword">final</span> container = ProviderContainer();

      <span class="hljs-keyword">final</span> dio = container.read(dioClientProvider);

      expect(dio, isA&lt;Dio&gt;());
      expect(dio.options.baseUrl, <span class="hljs-string">'https://v2.jokeapi.dev/joke'</span>);
    });
  });
}
</code></pre>
<blockquote>
<p>ℹ️ From de docs: <code>ProviderContainer</code> An object that stores the state of the providers and allows overriding the behavior of a specific provider.
If you are using Flutter, you do not need to care about this object (outside of testing), as it is implicitly created for you by <code>ProviderScope</code>.</p>
</blockquote>
<h2 id="heading-futureprovider-test">FutureProvider Test</h2>
<p>Hopefully you remember how earlier in the article I mention the importance of creating a n independent <code>dioProvider</code> so that we were able to mock it for testing purposes. Well we've reached that point. </p>
<p>We'll use <a target="_blank" href="https://pub.dev/packages/mocktail"><code>mocktail</code></a> to create <strong>mocks</strong> of the <code>Dio</code> http client and the <code>Response</code> object.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MockDio</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Mock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Dio</span> </span>{}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MockDioResponse</span>&lt;<span class="hljs-title">T</span>&gt; <span class="hljs-keyword">extends</span> <span class="hljs-title">Mock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Response</span>&lt;<span class="hljs-title">T</span>&gt; </span>{}
</code></pre>
<p>Before start writing the tests let's create a group for the <code>jokesFutureProvider</code> so we can keep our test file organized.</p>
<pre><code class="lang-dart"> group(<span class="hljs-string">'jokesFutureProvider'</span>, () {
    <span class="hljs-keyword">late</span> Dio client;

    setUp(() {
      client = MockDio();
    });
  });
</code></pre>
<p>To start, we can test the happy path of the <code>jokesFutureProvider</code>. We can test that the <code>FutureProvider</code> state first changes to <code>AsyncValue.loading</code> and if the http request success the state will change to <code>AsyncValue.data</code>. We can also validate that the http request is made to the correct url.</p>
<pre><code class="lang-dart">test(<span class="hljs-string">'returns AsyncValue.data when joke request success'</span>, () <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">const</span> jsonMap = &lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;{
    <span class="hljs-string">'error'</span>: <span class="hljs-keyword">false</span>,
    <span class="hljs-string">'category'</span>: <span class="hljs-string">'Programming'</span>,
    <span class="hljs-string">'type'</span>: <span class="hljs-string">'twopart'</span>,
    <span class="hljs-string">'setup'</span>: <span class="hljs-string">'Why are modern programming languages so materialistic?'</span>,
    <span class="hljs-string">'delivery'</span>: <span class="hljs-string">'Because they are object-oriented.'</span>,
    <span class="hljs-string">'id'</span>: <span class="hljs-number">21</span>,
    <span class="hljs-string">'safe'</span>: <span class="hljs-keyword">true</span>,
    <span class="hljs-string">'lang'</span>: <span class="hljs-string">'en'</span>,
  };

  <span class="hljs-keyword">final</span> response = MockDioResponse&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;();
  when(() =&gt; response.data).thenReturn(jsonMap);

  <span class="hljs-comment">// Use the mock response to answer any GET request made with the</span>
  <span class="hljs-comment">// mocked Dio client.</span>
  when(() =&gt; client.<span class="hljs-keyword">get</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;(any()))
      .thenAnswer((_) <span class="hljs-keyword">async</span> =&gt; response);

  <span class="hljs-keyword">final</span> container = ProviderContainer(
    overrides: [
      dioClientProvider.overrideWithValue(client),
    ],
  );

  <span class="hljs-comment">// Expect the first state to be AsyncValue.loading.</span>
  expect(container.read(jokesFutureProvider), AsyncValue&lt;Joke&gt;.loading());

  <span class="hljs-comment">// Read the value of `jokesFutureProvider` which will trigger the</span>
  <span class="hljs-comment">// Asynchronous operation.</span>
  <span class="hljs-keyword">await</span> container.read(jokesFutureProvider.future);

  <span class="hljs-comment">// Expect AsyncValue.data after the future completes.</span>
  expect(
    container.read(jokesFutureProvider),
    AsyncValue&lt;Joke&gt;.data(
      Joke.fromJson(jsonMap),
    ),
  );

  <span class="hljs-comment">// Verify the GET request was made to the correct URL.</span>
  verify(
    () =&gt; client.<span class="hljs-keyword">get</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;(<span class="hljs-string">'/Programming?type=twopart'</span>),
  ).called(<span class="hljs-number">1</span>);
});
</code></pre>
<p>Now we can test the the opposite scenario. What happens when an error occurs during the asynchronous operation of the <code>FutureProvider</code>?. We would expect to have an <code>AsyncValue.error</code>. Let's test that:</p>
<pre><code class="lang-dart">test(<span class="hljs-string">'returns AsyncValue.error when joke request throws'</span>, () <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> exception = Exception();

  <span class="hljs-comment">// Use the mocked Dio client to throw when any get request is made</span>
  when(() =&gt; client.<span class="hljs-keyword">get</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;(any())).thenThrow(exception);

  <span class="hljs-keyword">final</span> container = ProviderContainer(
    overrides: [
      dioClientProvider.overrideWithValue(client),
    ],
  );

  <span class="hljs-comment">// Expect the first state to be AsyncValue.loading.</span>
  expect(container.read(jokesFutureProvider), AsyncValue&lt;Joke&gt;.loading());

  <span class="hljs-comment">// Read the value of `jokesFutureProvider` which will trigger the</span>
  <span class="hljs-comment">// Asynchronous operation.</span>
  <span class="hljs-keyword">await</span> expectLater(
    container.read(jokesFutureProvider.future),
    throwsA(isA&lt;Exception&gt;()),
  );

  <span class="hljs-comment">// Expect AsyncValue.error after the future completes.</span>
  expect(
    container.read(jokesFutureProvider),
    isA&lt;AsyncError&lt;Joke&gt;&gt;().having((e) =&gt; e.error, <span class="hljs-string">'error'</span>, exception),
  );

  <span class="hljs-comment">// Verify the GET request was made to the correct URL.</span>
  verify(
    () =&gt; client.<span class="hljs-keyword">get</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;(<span class="hljs-string">'/Programming?type=twopart'</span>),
  ).called(<span class="hljs-number">1</span>);
});
</code></pre>
<h2 id="heading-ui-tests">UI Tests</h2>
<p>After testing the logic/state_management part of our application we can proceed to test the UI. So that we can ensure that the app renders the correct widgets for the correct states.</p>
<h3 id="heading-helper">Helper</h3>
<p>Inside the <code>test/</code> directory let's add a helper that will make the UI testing process easy.</p>
<pre><code class="lang-shell">test/
└── helpers/
    ├── helpers.dart (barrel file)
    └── pump_app.dart
</code></pre>
<p>And inside the <code>pump_app.dart</code> file let's add the following extension method.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_riverpod/flutter_riverpod.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_test/flutter_test.dart'</span>;

<span class="hljs-keyword">extension</span> PumpApp <span class="hljs-keyword">on</span> WidgetTester {
  Future&lt;<span class="hljs-keyword">void</span>&gt; pumpApp(
    Widget widget, {
    <span class="hljs-built_in">List</span>&lt;Override&gt; overrides = <span class="hljs-keyword">const</span> [],
  }) {
    <span class="hljs-keyword">return</span> pumpWidget(
      ProviderScope(
        overrides: overrides,
        child: MaterialApp(
          home: widget,
        ),
      ),
    );
  }
}
</code></pre>
<p>With this extension method we can add the <code>ProviderScope</code> and pass in some <code>overrides</code> for every test. </p>
<p>Now we can go to the <code>jokes_page_test.dart</code> file and write our tests.</p>
<h3 id="heading-jokespage-test">JokesPage Test</h3>
<p>Let's start creating the group for the <code>JokesPage</code> tests:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">void</span> main() {
  group(<span class="hljs-string">'JokesPage'</span>, () {
      <span class="hljs-comment">// We'll use this later</span>
      <span class="hljs-keyword">const</span> tJoke = Joke(
          error: <span class="hljs-keyword">false</span>,
          category: <span class="hljs-string">'category'</span>,
          type: <span class="hljs-string">'type'</span>,
          setup: <span class="hljs-string">'setup'</span>,
          delivery: <span class="hljs-string">'delivery'</span>,
          id: <span class="hljs-number">1</span>,
          safe: <span class="hljs-keyword">true</span>,
          lang: <span class="hljs-string">'lang'</span>,
    );

    <span class="hljs-comment">// tests go here ...</span>
  });
}
</code></pre>
<p>To mock the state of the <code>FutureProvider</code> during any test, we just need override the value of the <code>jokesFutureProvider</code> like this:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">await</span> tester.pumpApp(
  <span class="hljs-comment">// any Widget ...</span>
  overrides: [
    jokesFutureProvider.overrideWithValue(AsyncValue.loading()),
  ],
);
</code></pre>
<p>Knowing this, we can create the first test that simply validates if the <code>JokesPage</code> renders all the expected widgets.</p>
<pre><code class="lang-dart">testWidgets(<span class="hljs-string">'renders JokeWidget and ElevatedButton'</span>, (tester) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">await</span> tester.pumpApp(
    JokesPage(),
    overrides: [
      jokesFutureProvider.overrideWithValue(AsyncValue.loading()),
    ],
  );

  expect(find.byType(JokesPage), findsOneWidget);
  expect(find.byType(JokeWidget), findsOneWidget);
  expect(find.byType(ElevatedButton), findsOneWidget);
});
</code></pre>
<p>Now that you get how to the <code>overrides</code> let's test the loading, data and error cases for the <code>JokeWidget</code>:</p>
<pre><code class="lang-dart">group(<span class="hljs-string">'JokeWidget'</span>, () {
  <span class="hljs-keyword">final</span> errorTextFinder = find.byKey(Key(<span class="hljs-string">'jokesPage_jokeWidget_errorText'</span>));
  <span class="hljs-keyword">final</span> jokeContainerFinder =
      find.byKey(Key(<span class="hljs-string">'jokesPage_jokeWidget_jokeContainer'</span>));

  testWidgets(<span class="hljs-string">'renders CircularProgressIndicator for AsyncValue.loading'</span>,
      (tester) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> tester.pumpApp(
      JokesPage(),
      overrides: [
        jokesFutureProvider.overrideWithValue(AsyncValue.loading()),
      ],
    );

    expect(find.byType(CircularProgressIndicator), findsOneWidget);
    expect(jokeContainerFinder, findsNothing);
    expect(errorTextFinder, findsNothing);
  });

  testWidgets(<span class="hljs-string">'renders error text for AsyncValue.error'</span>, (tester) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> tester.pumpApp(
      JokesPage(),
      overrides: [
        jokesFutureProvider.overrideWithValue(AsyncValue.error(<span class="hljs-string">''</span>)),
      ],
    );

    expect(errorTextFinder, findsOneWidget);
    expect(jokeContainerFinder, findsNothing);
    expect(find.byType(CircularProgressIndicator), findsNothing);
  });

  testWidgets(<span class="hljs-string">'renders jokes container for AsyncValue.data'</span>,
      (tester) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> tester.pumpApp(
      JokesPage(),
      overrides: [
        jokesFutureProvider.overrideWithValue(AsyncValue.data(tJoke)),
      ],
    );

    expect(errorTextFinder, findsNothing);
    expect(find.byType(CircularProgressIndicator), findsNothing);
    expect(jokeContainerFinder, findsOneWidget);
  });
});
</code></pre>
<h3 id="heading-futureproviderrefresh">FutureProvider.refresh</h3>
<p>We are about to test the last part of our UI but this one can be tricky for some new developers. We need to test that the <code>jokesFutureProvider</code> is refreshed (it calls the asynchronous operation again) when the button in the <code>JokesPage</code> is pressed. </p>
<p>To do this you could think of trying to mock the <code>FutureProvider</code>, but this can be tricky and confusing. But we can mock the asynchronous operation that the <code>FutureProvider</code> executes and <code>verify</code> that it is called after pressing the button.</p>
<p>To do this we first need a function that can be mocked, to do this we could use a callable class:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MockFunction</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Mock</span> </span>{
  Future&lt;Joke&gt; call();
}
</code></pre>
<p>and the test will end up looking like this:</p>
<pre><code class="lang-dart">testWidgets(<span class="hljs-string">'refresh jokesFutureProvider when ElevatedButton is tapped'</span>,
    (tester) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> mockFunction = MockFunction();

  when(mockFunction.call).thenAnswer((_) =&gt; Future.value(tJoke));

  <span class="hljs-keyword">await</span> tester.pumpApp(
    JokesPage(),
    overrides: [
      jokesFutureProvider.overrideWithProvider(
        FutureProvider((_) =&gt; mockFunction.call()),
      ),
    ],
  );

  expect(find.byType(ElevatedButton), findsOneWidget);

  <span class="hljs-keyword">await</span> tester.tap(find.byType(ElevatedButton));

  verify(
    mockFunction.call,
  ).called(<span class="hljs-number">2</span>);
});
</code></pre>
<p>and just like that we are able to achieve 100% test coverage in our jokes application 🧪🎉.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645887719669/DxyMk1aIH.png" alt="image.png" /></p>
<h1 id="heading-final-thoughts">Final thoughts</h1>
<p>Before ending is my responsibility to remind you two things:</p>
<ol>
<li>Remember to test your code as you are making a favor to your future self.</li>
<li>Use <code>FutureProvider</code> in a responsible way, it is a great tool for some simple logic flows but always think through so that you can decide the best solution. </li>
</ol>
<p>Thanks for reading all the way! I hope this article was helpful.</p>
<p>If you would like to have any similar article covering another tool or state management solution, let me know in the comments. </p>
<p>If you got any questions feel free to contact me through any of my socials.</p>
]]></content:encoded></item><item><title><![CDATA[Cleaner Flutter Vol. 2: SOLID principles]]></title><description><![CDATA[💡 Note: This article is part of the Cleaner Flutter series by my friend Marcos Sevilla to which I contribute. You can find the complete series here.

After starting in the world of programming we all reach a point where we have to look back on the r...]]></description><link>https://elianortega.dev/cleaner-flutter-vol-2-solid-principles</link><guid isPermaLink="true">https://elianortega.dev/cleaner-flutter-vol-2-solid-principles</guid><category><![CDATA[Dart]]></category><category><![CDATA[Flutter Examples]]></category><category><![CDATA[clean code]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Elian Ortega]]></dc:creator><pubDate>Thu, 17 Feb 2022 03:52:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645067864578/Wco9yWxIB.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>💡 Note: This article is part of the Cleaner Flutter series by my friend <a target="_blank" href="https://marcossevilla.dev">Marcos Sevilla</a> to which I contribute. You can find the complete series <a target="_blank" href="https://marcossevilla.dev/series/cleaner-flutter">here</a>.</p>
</blockquote>
<p>After starting in the world of programming we all reach a point where we have to look back on the road and review some of the lines of code that we have written, either 1 day ago to remember an idea or years ago to review the implementation of any module of our software.</p>
<p>Many times in these glances at the code of the past we come across a list of problems such as:</p>
<ul>
<li>Having to search among many files for the answer to what we are looking for.</li>
<li>Not understanding the code we wrote.</li>
<li>Not understanding why we wrote the code the way we did.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645068332541/j0wbrnR3c.gif" alt="giphy (6).gif" /></p>
<p>These problems start when we start a project because we do not spend enough time to have a clear idea not only of what we are going to do but also of how we are going to do it.</p>
<p>We have to develop code imagining what would happen if I return in 2 years to review it, this ability to program clean and understandable code is essential to facilitate development, especially if you work in a team.</p>
<h1 id="heading-what-are-the-solid-principles">What are the SOLID principles?</h1>
<p>SOLID is the acronym for a set of principles that help us develop more maintainable code that also allows easy extension without compromising code already developed.</p>
<p>In other words...</p>
<blockquote>
<p><strong><em>Write more code without damaging what already works.</em></strong></p>
</blockquote>
<p>We can even see them as a set of guidelines to follow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645068026426/frhV_QzuY.png" alt="nkG5sEaAg.png" /></p>
<p>Now we are going to explore each of the principles, which can be applied to any programming language, but I am going to cover them using Dart language since it is the language used by the Flutter framework.</p>
<p>Before continuing it is important to note that these were first introduced by Uncle Bob. You can see his explanation <a target="_blank" href="https://www.youtube.com/watch?v=zHiWqnTWsn4">SOLID Principles by Uncle Bob</a>.</p>
<h1 id="heading-s-stands-for-single-responsibility">S stands for Single Responsibility</h1>
<blockquote>
<p><strong><em> A class must have one, and only one, a reason to change.</em></strong></p>
</blockquote>
<p>To explain this principle we can imagine a messy room, as we have all had it at some point, perhaps even now that you are reading this.</p>
<p>But the truth is that within this, everything has its place and everything should be in its designated place. To put the analogy aside, this principle tells us more specifically:</p>
<ul>
<li>A class must have a unique responsibility (applies to methods, variables, entities, etc).</li>
<li>There is a place for everything and everything should be in its place.</li>
<li>All the variables and methods of the class must be aligned with the objective of the class.</li>
</ul>
<p>By following these principles, we achieve smaller and simpler classes that have unique objectives. Also, we avoid giant classes with generic properties and methods that can be redundant in development.</p>
<p>Let's see an example:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645068669608/dle_5lYj9.gif" alt="giphy (7).gif" /></p>
<p>Let's take a look at this <code>signUp</code> function. This could be the method we call from our UI layer to perform the user sign-up process.</p>
<pre><code class="lang-dart">Future&lt;User&gt; signUp({
  <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> name,
  <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> email,
  <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> password,
}) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> isValid = name.isNotEmpty &amp;&amp; email.isNotEmpty &amp;&amp; password.isNotEmpty;

  <span class="hljs-keyword">if</span> (isValid) {
    <span class="hljs-keyword">final</span> newUser = User(
      name: name,
      email: email,
      password: password,
    );

    <span class="hljs-keyword">final</span> userMap = newUser.toMap();

    <span class="hljs-keyword">await</span> saveToDb(json.encode(userMap));
  }
}
</code></pre>
<p>In the code, we see that there is the functionality of creation, conversion to JSON, and even the call to the database that is normally an API call, so we are not fulfilling the principle.</p>
<p>This is one of the most common mistakes, especially in Flutter, since developers easily make the mistake of combining different things within the same class or methods.</p>
<blockquote>
<p><strong><em>⚠️ In other articles we will see how this applies to Clean Architecture...</em></strong></p>
</blockquote>
<p>Done, I got it, now... how do I apply it?</p>
<p>To follow the single responsibility principle correctly, we could create methods and classes with simple and unique functionalities.</p>
<p>In the example method <code>signUp</code> many things are done with different objectives, each of these functionalities could be separated into an individual class with a single objective.</p>
<h2 id="heading-extracting-logic">Extracting logic</h2>
<p>We could implement a class that is responsible for performing the validation, there are many ways to do this. One of them can be to use <code>package:formz</code> which is a Dart package that allows us to create classes for a data type and perform validations.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:formz/formz.dart'</span>;

<span class="hljs-comment">/// <span class="markdown">Define input validation errors</span></span>
<span class="hljs-keyword">enum</span> NameInputError { empty }

<span class="hljs-comment">/// <span class="markdown">Extend FormzInput and provide the input type and error type.</span></span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NameInput</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">FormzInput</span>&lt;<span class="hljs-title">String</span>, <span class="hljs-title">NameInputError</span>&gt; </span>{
  <span class="hljs-comment">/// <span class="markdown">Call super.pure to represent an unmodified form input.</span></span>
  <span class="hljs-keyword">const</span> NameInput.pure() : <span class="hljs-keyword">super</span>.pure(<span class="hljs-string">''</span>);

  <span class="hljs-comment">/// <span class="markdown">Call super.dirty to represent a modified form input.</span></span>
  <span class="hljs-keyword">const</span> NameInput.dirty({<span class="hljs-built_in">String</span> value = <span class="hljs-string">''</span>}) : <span class="hljs-keyword">super</span>.dirty(value);

  <span class="hljs-comment">/// <span class="markdown">Override validator to handle validating a given input value.</span></span>
  <span class="hljs-meta">@override</span>
  NameInputError? validator(<span class="hljs-built_in">String</span> value) {
    <span class="hljs-keyword">return</span> value.isNotEmpty == <span class="hljs-keyword">true</span> ? <span class="hljs-keyword">null</span> : NameInputError.empty;
  }
}
</code></pre>
<p>Do not focus too much on the logic of the code, the important thing is to understand that now the validation is decoupled from the rest of the logic with the <code>validator</code> method.</p>
<h2 id="heading-connecting-dependencies">Connecting dependencies</h2>
<p>The other error is to call an API from the business logic or user interface, this would not fulfill the principle since the connection with the API is a complex functionality by itself, so it would be best to implement a class as <code>Repository</code> to which we pass the parameters that we are going to send and delegate the rest of the process to it.</p>
<pre><code class="lang-dart"><span class="hljs-comment">/// <span class="markdown">Repository that handles the relationship</span></span>
<span class="hljs-comment">/// <span class="markdown">between app logic and data source or API.</span></span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthRepository</span> </span>{
  AuthRepository({
    http.Client? client,
  }) : _client = client ?? http.Client();

  <span class="hljs-keyword">final</span> http.Client _client;

  <span class="hljs-comment">/// <span class="markdown">Method that makes the API call to sign-up a user.</span></span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; signUpUser(User user) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> _client.post(
        <span class="hljs-built_in">Uri</span>.parse(<span class="hljs-string">'https://noscope.dev/user/create'</span>),
        body: user.toMap(),
      );
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-keyword">throw</span> NetworkException();
    }
  }
}
</code></pre>
<p>This repository concept is key to meeting clean architecture standards but we can see that the principle of single responsibility is behind this whole idea.</p>
<p>By implementing these classes applying the principle we would achieve a simpler method compared to how we started with:</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; signUp({
  <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> name,
  <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> password,
  <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> email,
}) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> newUser = User(
    email: email,
    password: password,
    name: name,
  );
  <span class="hljs-keyword">await</span> _authRepository.signUpUser(newUser);
}
</code></pre>
<p>Simpler, testable, more maintainable, and decoupled.</p>
<h1 id="heading-o-stands-for-openclosed">O stands for Open/Closed</h1>
<blockquote>
<p><strong><em>An entity must be open to extending but closed to modify.</em></strong></p>
</blockquote>
<p>This principle tells us that we must extend our classes to add new code, instead of modifying the existing one.</p>
<p>The first time we read this it can be a bit confusing but it just is:</p>
<blockquote>
<p><strong><em>Don't modify what already works, just extend and add new code.</em></strong></p>
</blockquote>
<p>In this way, we can develop without damaging the previously tested code. To understand this principle we can see an <a target="_blank" href="https://www.youtube.com/watch?v=rtmFCcjEgEw">example</a> given by Katerina Trjchevska at LaraconEU 2018.</p>
<p>Let's imagine that our app has a payment module that currently only accepts debit/credit cards and PayPal as payment methods.</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; pay(<span class="hljs-built_in">String</span> paymentType) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">if</span> (paymentType == <span class="hljs-string">'card'</span>) {
    <span class="hljs-keyword">await</span> _paymentRepository.payWithCard();
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (paymentType == <span class="hljs-string">'paypal'</span>) {
    <span class="hljs-keyword">await</span> _paymentRepository.payWithPaypal();
  }
}
</code></pre>
<p>At a first glance at the code, we may think that everything is fine, but when we analyze its long-term <strong>scalability</strong>, we realize the problem.</p>
<p>Let's imagine that our client asks us to add a new payment method such as Alipay, gift cards, and others.</p>
<p>Each new payment method implies a new function and a new conditional in the <code>pay</code> method, and we could say that this is not an issue, but if we keep adding code within the same class, we would never achieve a stable, ready for production code.</p>
<p>By applying the open/closed principle, we can create an abstract class <code>PayableInterface</code> that serves as a payment interface. In this way, each of our payment methods extends this abstract class, and it can be a separate class that is not affected by modifications made to another.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PayableInterface</span> </span>{
  Future&lt;<span class="hljs-keyword">void</span>&gt; pay();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CardPayment</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">PayableInterface</span> </span>{
  <span class="hljs-meta">@override</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; pay() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// Card payment logic.</span>
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaypalPayment</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">PayableInterface</span> </span>{
  <span class="hljs-meta">@override</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; pay() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// Paypal payment logic.</span>
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AliPayPayment</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">PayableInterface</span> </span>{
  <span class="hljs-meta">@override</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; pay() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// AliPay payment logic.</span>
  }
}
</code></pre>
<p>After having our payment logic implemented, we can receive a parameter with the <code>paymentType</code> that allows us to select the <code>PayableInterface</code> indicated for the transaction, and in this way we do not have to worry about how the <code>pay</code> method makes the payment, only to make a type of filtering so that the correct instance of the interface is used; be it Card, PayPal or Alipay.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">enum</span> PaymentType { card, payPal, aliPay }

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentFactory</span> </span>{
  <span class="hljs-keyword">const</span> PaymentFactory();

  PayableInterface initializePayment(PaymentType paymentType) {
    <span class="hljs-comment">// Filter by `paymentType` and return the correct [PayableInterface].</span>
    <span class="hljs-keyword">switch</span> (paymentType) {
      <span class="hljs-keyword">case</span> PaymentType.card:
        <span class="hljs-keyword">return</span> CardPayment();
      <span class="hljs-keyword">case</span> PaymentType.payPal:
        <span class="hljs-keyword">return</span> PayPalPayment();
      <span class="hljs-keyword">case</span> PaymentType.aliPay:
        <span class="hljs-keyword">return</span> AliPayPayment();
    }
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentRepository</span> </span>{
  PaymentRepository({
    <span class="hljs-keyword">required</span> PaymentFactory paymentFactory,
  }) : _paymentFactory = paymentFactory;

  <span class="hljs-keyword">final</span> PaymentFactory _paymentFactory;

  Future&lt;<span class="hljs-keyword">void</span>&gt; pay(PaymentType paymentType) <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// Get the respective payableInterface based on the `paymentType`.</span>
    PayableInterface payment = _paymentFactory.initializePayment(paymentType);

    <span class="hljs-comment">// Make payment.</span>
    payment.pay();
  }
}
</code></pre>
<p>In the end, we would have a method like this where we can see that the code was reduced to only 3 lines and it is much easier to read.</p>
<p>It is also more scalable since if we wanted to add a new type of payment method we would only have to extend from <code>PayableInterface</code> and add it as an option in the filtering method.</p>
<blockquote>
<p><strong><em>👆 These concepts of abstractions and instances are confusing at first but throughout this series of articles and by practice they'll become simple concepts.</em></strong></p>
</blockquote>
<h1 id="heading-l-stands-for-liskov-substitution">L stands for Liskov Substitution</h1>
<blockquote>
<p><strong><em>We can change any concrete instance of a class with any class that implements the same interface.</em></strong></p>
</blockquote>
<p>The main objective of this principle is that we should always obtain the expected behavior of the code regardless of the class instance that is being used.</p>
<p>To be able to fulfill this principle correctly, there are 2 important parts:</p>
<ul>
<li>Implementation.</li>
<li>Abstraction.</li>
</ul>
<p>The first we can see in the previous example of open/closed principle when we have <code>PayableInterface</code> and the payment methods that implement it as <code>CardPayment</code> and <code>PaypalPayment</code></p>
<p>In the implementation of the code we see that it doesn't matter with the implementation we choose, our code should continue to work correctly, this is because both make correct implementation of the <code>PayableInterface</code> interface.</p>
<p>With this example, the idea is easy to understand but in practice, there are many times that we perform the abstraction process wrong, so we cannot truly make great use of the principle.</p>
<p>If you are not very familiar with concepts such as interface, implementation, and abstraction this may sound a bit complex but let's see it with a simple example..</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645069253150/lyfZGb1fM.jpeg" alt="ilxzO.jpeg" /></p>
<p>This is one of the iconic images of the principle as it makes it easy to understand.</p>
<p>Let's imagine that in our code we have a class called <code>DuckInterface</code>.</p>
<p>This gives us the basic functionality of a duck: fly, swim, and quack. And we would have the <code>RubberDuck</code> class that implements the interface.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DuckInterface</span> </span>{
  <span class="hljs-keyword">void</span> fly();
  <span class="hljs-built_in">String</span> swim();
  <span class="hljs-built_in">String</span> quack();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RubberDuck</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DuckInterface</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> fly() {
    <span class="hljs-keyword">throw</span> Exception(<span class="hljs-string">'Rubber duck cannot fly'</span>);
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">String</span> quack() {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Quack!!!"</span>;
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">String</span> swim() {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Duck Swimming"</span>;
  }
}
</code></pre>
<p>At a first glance, we could say that our abstraction is fine since we are using an interface that gives us the functionality we need, but the <code>fly</code> method does not apply to a rubber duck, imagine that our program is going to have different Animals with shared functionality such as flying and swimming, so it would not make sense to leave this method on the <code>DuckInterface</code> interface.</p>
<p>To solve this and comply with the Liskov principle, we can create more specific interfaces that allow us to reuse code, which also makes our code more maintainable.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">QuackInterface</span> </span>{
  <span class="hljs-built_in">String</span> quack();
}

<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FlyInterface</span> </span>{
  <span class="hljs-built_in">String</span> fly();
}

<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SwimInterface</span> </span>{
  <span class="hljs-built_in">String</span> swim();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RubberDuck</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">QuackInterface</span>, <span class="hljs-title">SwimInterface</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">String</span> quack() {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Quack!!!"</span>;
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">String</span> swim() {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Duck Swimming"</span>;
  }
}
</code></pre>
<p>With this implementation, our <code>RubberDuck</code> class only implements the methods it needs and now, for example, if we need an animal that fulfills a specific function such as swimming, we could use any class that implements the <code>SwimInterface</code> interface. This is because by fulfilling the Liskov principle we can switch any declaration of an <code>abstract class</code> by any class that implements it.</p>
<h1 id="heading-i-stands-for-interface-segregation">I stands for Interface Segregation</h1>
<blockquote>
<p><strong><em>The code should not depend on methods that it does not use.</em></strong></p>
</blockquote>
<p>At first, this could seem to be the simplest principle but for this very reason, in the beginning, it can even confuse us.</p>
<p>In the previous principles, we have seen the importance of interfaces to decouple our code.</p>
<p>This principle ensures that our abstractions for creating interfaces are correct since we cannot create a new instance of an interface without implementing one of the methods defined by them. The above would be violating the principle</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645069396123/nNFB7_L0A.jpeg" alt="InterfaceSegregationPrinciple.jpeg" /></p>
<p>This image shows the problem of not fulfilling this principle, we have some instances of classes that do not use all the interface methods, which lead to a dirty code and indicate bad abstraction.</p>
<p>It is easier to see it with the typical anima example, this is very similar to the example we saw from Liskov.</p>
<blockquote>
<p><strong><em>💡 At this point the examples become similar but the important thing is to see the code from another perspective.</em></strong></p>
</blockquote>
<p>We have an abstract class <code>Animal</code> that is our interface, it has 3 methods defined <code>eat</code>, <code>sleep</code>, and <code>fly</code>.</p>
<p>If we create a <code>Bird</code> class that implements the animal interface we don't see any problem, but what if we want to create the <code>Dog</code> class?</p>
<p>Exactly, we realize that we cannot implement the <code>fly</code> method because it does not apply to a dog.</p>
<p>We could leave it like that and avoid the time needed to restructure the code since we logically know that this would not affect our code, but this breaks the principle.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Animal</span> </span>{
  <span class="hljs-keyword">void</span> eat();
  <span class="hljs-keyword">void</span> sleep();
  <span class="hljs-keyword">void</span> fly();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Bird</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Animal</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> eat() {}

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> sleep() {}

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> fly() {}
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Dog</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Animal</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> eat() {}

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> sleep() {}

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> fly() =&gt; <span class="hljs-keyword">throw</span> Exception(<span class="hljs-string">'Dogs cannot fly!'</span>);
}
</code></pre>
<p>The mistake is made by having a bad abstraction in our class and the right thing to do is always refactor to ensure that the principles are being met.</p>
<p>It may take us a little longer at the moment but the results of having a clean and scalable code should always be priorities.</p>
<p>A solution to this could be that our <code>Animal</code> interface only has the methods shared by animals like <code>eat</code>, <code>sleep</code> and we create another interface for the <code>fly</code> method. In this way, only animals that need this method to implement its interface.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FlyInterface</span> </span>{
  <span class="hljs-keyword">void</span> fly();
}

<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Animal</span> </span>{
  <span class="hljs-keyword">void</span> eat();
  <span class="hljs-keyword">void</span> sleep();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Bird</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Animal</span>, <span class="hljs-title">FlyInterface</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> eat() {}

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> sleep() {}

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> fly() {}
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Dog</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Animal</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> eat() {}

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> sleep() {}
}
</code></pre>
<h1 id="heading-d-stands-for-dependency-inversion">D stands for Dependency Inversion</h1>
<blockquote>
<p>💉 <strong><em>High-level modules should not depend on low-level modules. Both must depend on abstractions.</em></strong></p>
</blockquote>
<p>This principle tells us:</p>
<ul>
<li><p>You never have to depend on a concrete implementation of a class, only on its abstractions (interfaces).</p>
</li>
<li><p>Same as the image presented on <a target="_blank" href="https://marcossevilla.dev/cleaner-flutter-vol-1"><strong>volume 1</strong></a>, we follow the rule that high-level modules should not strictly rely on low-level modules.</p>
</li>
</ul>
<p>To understand it more simply, let's look at the example.</p>
<p>Nowadays, every app or software that is developed needs to communicate with the outside world. Normally this is done through code repositories that we instantiate and call from the business logic in our software.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataRepository</span> </span>{
  Future&lt;<span class="hljs-keyword">void</span>&gt; logInUser() <span class="hljs-keyword">async</span> {}

  Future&lt;<span class="hljs-keyword">void</span>&gt; signUpUser() <span class="hljs-keyword">async</span> {}
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BusinessLogic</span> </span>{
  <span class="hljs-keyword">const</span> BusinessLogic({
    <span class="hljs-keyword">required</span> DataRepository repository,
  }) : _repository = repository;

  <span class="hljs-keyword">final</span> DataRepository _repository;

  Future&lt;<span class="hljs-keyword">void</span>&gt; logIn() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _repository.logInUser();
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; signUp() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _repository.signUpUser();
  }
}
</code></pre>
<p>Declaring and using a concrete class, such as the <code>DataRepository</code> within <code>BusinessLogic</code>, is a very common practice and is one of the common mistakes that make our code not very scalable. By depending on a particular instance of a class we surely know it will never be stable because you are constantly adding code to it.</p>
<p>To solve this problem, the principle tells us to create an interface that communicates both modules. You can even develop the whole functionality of the business logic and user interface of an app by depending on an interface that hasn't been implemented.</p>
<p>This also allows better communication in a team of developers because when creating an interface, everyone is clear about the objectives of the module, and from that definition, it can be verified that the SOLID principles are being met.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataRepositoryInterface</span> </span>{
  Future&lt;<span class="hljs-keyword">void</span>&gt; logInUser();

  Future&lt;<span class="hljs-keyword">void</span>&gt; signUpUser();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">DataRepositoryInterface</span> </span>{
  <span class="hljs-meta">@override</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; logInUser() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// Implementation...</span>
  }

  <span class="hljs-meta">@override</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; signUpUser() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// Implementation...</span>
  }
}
</code></pre>
<p>With this implementation, we create a <code>DataRepositoryInterface</code> that we can then implement in <code>DataRepository</code> and the magic happens inside the class that uses this functionality when we do not depend on a concrete instance but instead on an interface we could pass as parameters any concrete class that implements this interface.</p>
<p>It could be a local or external database and that would not affect the development, I repeat it: <strong>we do not depend on a single concrete instance, we can use any class that complies with the implementation of the interface.</strong></p>
<p>And this is what allows us to fulfill the magic word of clean architecture: decoupling!</p>
<h1 id="heading-wrap-up">Wrap-up</h1>
<p>I remind you that these are principles, not rules, so there is no single way to follow them, their use and compliance with the code will depend on each project since for many of these the objectives of the project are key to making decisions. Just as something within the scope of a project can be considered small it may under other requirements become something large.</p>
<h1 id="heading-continue-with-the-cleaner-flutter-series">Continue with the Cleaner Flutter Series</h1>
<p>You can go back to the series list <a target="_blank" href="https://marcossevilla.dev/series/cleaner-flutter">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[asdf Flutter version manager (MacOS/ Linux)]]></title><description><![CDATA[Without a doubt, Flutter is a framework that is constantly changing. And as developers, we need to be ready and have more than one version installed at the same time.
One of the most famous solutions for this problem is Flutter Version Manager by leo...]]></description><link>https://elianortega.dev/asdf-flutter-version-manager-macos-linux</link><guid isPermaLink="true">https://elianortega.dev/asdf-flutter-version-manager-macos-linux</guid><category><![CDATA[version control]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Flutter SDK]]></category><category><![CDATA[Dart]]></category><dc:creator><![CDATA[Elian Ortega]]></dc:creator><pubDate>Sun, 06 Feb 2022 18:36:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644172486639/jmEmtqBEF.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Without a doubt, <a target="_blank" href="https://flutter.dev/">Flutter</a> is a framework that is constantly changing. And as developers, we need to be ready and have more than one version installed at the same time.</p>
<p>One of the most famous solutions for this problem is <a target="_blank" href="https://github.com/leoafarias/fvm">Flutter Version Manager by leofarias</a>. I personally used this solution for several months, but I always had some problems with it.</p>
<p>A few weeks ago I came across <a target="_blank" href="https://asdf-vm.com/">asdf</a>, which according to its documentation:</p>
<blockquote>
<p><code>asdf</code> is a tool version manager. All tool version definitions are contained within one file (<code>.tool-versions</code>) which you can check in to your project's Git repository to share with your team, ensuring everyone is using the exact same versions of tools.</p>
</blockquote>
<p><img src="https://media.giphy.com/media/3oKIPqsXYcdjcBcXL2/giphy-downsized-large.gif" alt="https://media.giphy.com/media/3oKIPqsXYcdjcBcXL2/giphy-downsized-large.gif" /></p>
<p>In a nutshell, it allows us to handle multiple versions of different tools (which I found interesting as it could be useful for other tools like: <code>cocoapods</code>,<code>ruby</code>,<code>node.js</code>, etc.) </p>
<h2 id="heading-how-to-use-asdf-with-flutter">How to use asdf with flutter?</h2>
<p>Official docs: <a target="_blank" href="https://asdf-vm.com/guide/getting-started.html#_1-install-dependencies">here</a></p>
<h3 id="heading-install-asdf">Install asdf</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># MacOS with Homebrew</span>
brew install asdf

<span class="hljs-comment"># Linux</span>
<span class="hljs-comment"># Check: https://asdf-vm.com/guide/getting-started.html#_1-install-dependencies</span>
</code></pre>
<h3 id="heading-asdf-flutter-plugin">ASDF Flutter plugin</h3>
<p>Once asdf is installed, we have to install the asdf flutter plugin</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Installs flutter-plugin for asdf</span>
asdf plugin-add flutter
</code></pre>
<p><a target="_blank" href="https://github.com/oae/asdf-flutter">Github Repo: asdf flutter plugin</a></p>
<h3 id="heading-installuninstall-a-version">install/uninstall a version</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># List all available flutter versions</span>
asdf list all flutter
</code></pre>
<p>Preview:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iql8kn8mjtqju4igp2do.png" alt="carbon" /> </p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install a version </span>
asdf install flutter &lt;VERSION_NUMBER&gt;

<span class="hljs-comment"># Example:</span>
asdf install flutter 2.5.2-stable

<span class="hljs-comment"># to uninstall</span>
asdf uninstall flutter &lt;VERSION_NUMBER&gt;
</code></pre>
<h3 id="heading-local-and-global-version">Local and global version</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Defines a global flutter version to be used </span>
asdf global flutter &lt;VERSION_NUMBER&gt;

<span class="hljs-comment"># Defines a local flutter version to be used </span>
asdf <span class="hljs-built_in">local</span> flutter &lt;VERSION_NUMBER&gt;
</code></pre>
<h2 id="heading-export-to-dollarpath">Export to $PATH</h2>
<p>To have access to the currently selected version, you need to export asdf flutter to  <code>$PATH</code>.In my case, I added the following line to my <code>/.zshrc.</code></p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> PATH=<span class="hljs-string">"<span class="hljs-variable">$PATH</span>:<span class="hljs-subst">$(asdf where flutter)</span>/bin"</span>
<span class="hljs-built_in">export</span> PATH=<span class="hljs-string">"<span class="hljs-variable">$PATH</span>"</span>:<span class="hljs-string">"<span class="hljs-variable">$HOME</span>/.pub-cache/bin"</span>
</code></pre>
<p>With this you will have access to the <code>flutter</code> and <code>dart</code> commands from any terminal 👍.</p>
<h2 id="heading-and-dart">... and dart?</h2>
<p><code>dart</code> is included by default with the <code>asdf flutter plugin</code>. However, sometimes you need a different <code>dart</code> version. This can be accomplished by running the same commands mentioned above, but substituting <code>flutter</code> with <code>dart</code>. </p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install asdf-dart</span>
asdf plugin-add dart https://github.com/patoconnor43/asdf-dart.git

<span class="hljs-comment"># More info in: https://github.com/PatOConnor43/asdf-dart</span>
</code></pre>
<h3 id="heading-as-always">As always...</h3>
<p>You can share this article to help another developer to continue improving their productivity when writing applications with Flutter.</p>
]]></content:encoded></item></channel></rss>