<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Dan Patacean on Medium]]></title>
        <description><![CDATA[Stories by Dan Patacean on Medium]]></description>
        <link>https://medium.com/@dan.patacean?source=rss-9432c7746a3c------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*eUL8VNlX2SjlGT-pEH0XfA.png</url>
            <title>Stories by Dan Patacean on Medium</title>
            <link>https://medium.com/@dan.patacean?source=rss-9432c7746a3c------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 09:03:56 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@dan.patacean/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Flutter Stories — Advanced dependency Injection with injectable and getIt]]></title>
            <link>https://medium.com/@dan.patacean/flutter-stories-advanced-dependency-injection-with-injectable-and-getit-1d71d21dea01?source=rss-9432c7746a3c------2</link>
            <guid isPermaLink="false">https://medium.com/p/1d71d21dea01</guid>
            <category><![CDATA[injectable]]></category>
            <category><![CDATA[get-it]]></category>
            <category><![CDATA[dependency-injection]]></category>
            <category><![CDATA[flutter]]></category>
            <dc:creator><![CDATA[Dan Patacean]]></dc:creator>
            <pubDate>Sun, 18 Sep 2022 07:56:05 GMT</pubDate>
            <atom:updated>2022-09-18T07:56:05.669Z</atom:updated>
            <content:encoded><![CDATA[<h3>Flutter Stories — Advanced dependency Injection with injectable and getIt</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3I6Uzgko_ntPsWCNwkaBlg.png" /></figure><p>Hey there my fellow developers, <strong>_dev1, dev2 </strong>and pretty sure <strong>_dev3. </strong>In our previous <a href="https://medium.com/@dan.patacean/flutter-stories-dependency-injection-with-injectable-and-getit-fb95ee7758df">article</a> we created a simple but fun example app using DI. We didn’t go in too much details since that wasn’t the purpose of the story, but in this article we will do some cool configs and I’ll expand on some topics that were kind of ignored in the first step.</p><p>Let’s start with a simple one.</p><h3><strong>@Singleton / </strong>@LazySingleton</h3><p>If you don’t remember or you didn’t check the first story, we used @singleton to provide our <strong><em>Dio </em></strong>instance to the FactClient on the networking layer.</p><p>Now everybody (even you, <strong>_dev1) </strong>knows what singletons are, what is interesting about injectable and get_it is that you can provide a disposable method to your injectable singleton that will be called when the getIt context is popped or when the getIt context is reset. This can be used, for example when you log out a user and want to clear all it’s data. You don’t have to remember where and when to clear the data. Injectable &amp; get_it will do it for you.</p><h3>Binding abstract classes to implementations</h3><p>In our sample app, most of the networking implementation and the data layer are extending abstract classes, in order to inject abstract classes, or better say, in order to inject implementations for abstract classes we used <strong><em>@Inject(as:)</em> </strong>or <strong><em>@Singleton(as:)</em></strong><em>.</em> Combining the annotations with the abstract classes will allow you to inject different implementations for same abstract class. The full power of injectable is obtained when we add in the mix the <em>@named</em> annotation.</p><p>Let’s see how easy is to switch from cat facts to….well…Chuck Norris funny facts.</p><p><strong>Let’s do some refactoring first</strong></p><p>The rest client will have a dynamic path which will be provided via IConfig and now it will return an <em>ApiResponse</em> which will be a simple object containing a JSON.</p><pre><strong>import &#39;package:dependency_injection/networking/models/api_response.dart&#39;</strong>;<br><strong>import &#39;package:dio/dio.dart&#39;</strong>;<br><strong>import &#39;package:retrofit/retrofit.dart&#39;</strong>;<br><br><strong>part &#39;rest_client.g.dart&#39;</strong>;<br><br>@RestApi(baseUrl: <strong>&quot;https://catfact.ninja/&quot;</strong>)<br><strong>abstract class </strong>RestClient {<br>  <strong>factory </strong>RestClient(Dio dio, {String baseUrl}) = _RestClient;<br><br>  @GET(<strong>&quot;/{path}&quot;</strong>)<br>  Future&lt;ApiResponse&gt; randomFact(@Path() String path);<br>}</pre><p>The New App Config</p><pre>@Injectable(as: IConfig)<br><strong>class </strong>AppConfig <strong>extends </strong>IConfig {<br>  @override<br>  String <strong>get baseUrl </strong>=&gt; <strong>&quot;https://catfact.ninja/&quot;</strong>;<br><br>  @override<br>  Map&lt;String, String&gt; <strong>get headers </strong>=&gt; {};<br><br>  @override<br>  String <strong>get path </strong>=&gt; <strong>&#39;fact&#39;</strong>;<br>}</pre><p>The ApiResponse</p><pre><strong>class </strong>ApiResponse {<br>  <strong>final </strong>Map&lt;String, <strong>dynamic</strong>&gt; <strong>rawResponse</strong>;<br><br>  <strong>factory </strong>ApiResponse.fromJson(Map&lt;String, <strong>dynamic</strong>&gt; json) {<br>    <strong>return </strong>ApiResponse(rawResponse: json);<br>  }<br><br>  Map&lt;String, <strong>dynamic</strong>&gt; toJson() =&gt; <strong>rawResponse</strong>;<br><br>  <strong>const </strong>ApiResponse({<br>    <strong>required this</strong>.<strong>rawResponse</strong>,<br>  });<br>}</pre><p>That’s all for the refactoring part</p><p>Now let’s add a new config that will provide the new URL and path to our Chuck Norris facts:</p><pre>@Named(<strong>&#39;ChuckConfig&#39;</strong>) //THIS IS IMPORTANT<br>@Injectable(as: IConfig)<br><strong>class </strong>ChuckConfig <strong>extends </strong>IConfig {<br>  @override<br>  String <strong>get baseUrl </strong>=&gt; <strong>&quot;https://api.chucknorris.io/&quot;</strong>;<br><br>  @override<br>  Map&lt;String, String&gt; <strong>get headers </strong>=&gt; {};<br><br>  @override<br>  String <strong>get path </strong>=&gt; <strong>&#39;jokes/random&#39;</strong>;<br>}</pre><p>And now let’s create a new FactClient that will use the new config</p><pre>@Named(<strong>&#39;ChuckFactClient&#39;</strong>)<br>@Injectable(as: FactClient)<br><strong>class </strong>ChuckFactClient <strong>extends </strong>CatFactClient {<br>  ChuckFactClient({<br>    <strong>required </strong>Dio dio,<br>    @Named(<strong>&#39;ChuckConfig&#39;</strong>) required IConfig config,<br>  }) : <strong>super</strong>(dio: dio, config: config);</pre><pre>  //we just override the way the json is used to create the fact<br>  @override<br>  Fact _fromResponse(Map&lt;String, <strong>dynamic</strong>&gt; json) {<br>    String data = <strong>&quot;</strong>${json[<strong>&#39;value&#39;</strong>]}<strong>&quot;</strong>;<br>    <strong>return </strong>Fact(data, data.<strong>length</strong>);<br>  }<br>}</pre><p>Well that’s all for the networking, the Chuck Norris facts should flow fine :).</p><p>Now, the hard part, let’s change the implementation of our Repo and BLoC.</p><p><strong>The repo changes</strong></p><p>We have to change the constructor of the repo with the following code</p><pre>CatFacts({<br>  @Named(<strong>&#39;ChuckFactClient&#39;</strong>) required <strong>this</strong>.<strong>factClient</strong>,<br>});</pre><p><strong>The BLoC changes:</strong></p><p>Just kidding, there’s no BLoC changes, all we had to do is request a <em>ChuckFactClient </em>in the repo to switch the facts.</p><p>Now before you run the app, don’t forget to summon <em>Jeff</em> aka build_runner, to regenerate the code.</p><p><strong>One more cool annotation for this section</strong></p><p>We used <em>@Named()</em> annotation to better identify what to inject. But if you are like me, when it comes to name a variable or in this case an implementation, and you have only bad name ideas, injectable comes with another cool feature, the <strong>@named</strong> annotation or auto tagging.</p><p>This annotation will automatically assign the implementation class name to the instance name.</p><p>Here’s how you declare it:</p><pre><strong>@named</strong><br>@Injectable(as: FactClient)<br><strong>class </strong>ChuckFactClient <strong>extends </strong>CatFactClient {<br>  ChuckFactClient({<br>    <strong>required </strong>Dio dio,<br>    @Named(<strong>&#39;ChuckConfig&#39;</strong>) required IConfig config,<br>  }) : <strong>super</strong>(dio: dio, config: config);<br><br>  @override<br>  Fact _fromResponse(Map&lt;String, <strong>dynamic</strong>&gt; json) {<br>    String data = <strong>&quot;</strong>${json[<strong>&#39;value&#39;</strong>]}<strong>&quot;</strong>;<br>    <strong>return </strong>Fact(data, data.<strong>length</strong>);<br>  }<br>}</pre><p>And this is how you use it:</p><pre>@Injectable(as: FunFactRepo)<br><strong>class </strong>CatFacts <strong>extends </strong>FunFactRepo {<br>  <strong>final </strong>FactClient <strong>factClient</strong>;<br><br>  CatFacts({<br>    <strong>@Named.from(ChuckFactClient) </strong>required <strong>this</strong>.<strong>factClient</strong>,<br>  });<br><br>  @override<br>  Future&lt;Fact&gt; oneMoreFactPlease() <strong>async </strong>{<br>    <strong>try </strong>{<br>      <strong>return await factClient</strong>.randomFact();<br>    } <strong>catch </strong>(e) {<br>      <strong>return </strong>Fact(<strong>&quot;The fact is you have an error: </strong>${e.toString()}<strong>&quot;</strong>, -1);<br>    }<br>  }<br>}</pre><p>I honestly ❤️ this feature!!!</p><h3>Inject based on environments</h3><p>Yep, <em>injectable</em> has this feature too. No more <em>if(dev) do this else do this.</em></p><p>I know all of you had at least one project where you had different environments configured, maybe the backed had a staging and a prod version or maybe you had some internal builds. With injectable you can define your own environments and inject accordingly.</p><p>For the sake of practice let’s setup two envs for our app, well named, the cat env and the chuck env. You can name them like any other normal dev, test and production. Based on the selected env, we will load, you guest it, cat facts or Chuck facts.</p><p><strong>The env setup:</strong></p><pre><strong>import &#39;package:dependency_injection/di/injector.config.dart&#39;</strong>;<br><strong>import &#39;package:get_it/get_it.dart&#39;</strong>;<br><strong>import &#39;package:injectable/injectable.dart&#39;</strong>;<br><br><strong>final </strong>getIt = GetIt.<em>instance</em>;<br><br><em>//Define the Environments here<br></em><strong>const </strong>cat = Environment(<strong>&#39;cat&#39;</strong>);<br><strong>const </strong>chuck = Environment(<strong>&#39;chuck&#39;</strong>);<br><br>@InjectableInit(<br>  initializerName: <strong>r&#39;$initGetIt&#39;</strong>, <em>// default<br>  </em>preferRelativeImports: <strong>true</strong>, <em>// default<br>  </em>asExtension: <strong>false</strong>, <em>// default<br></em>)<br><strong>void </strong>configureDependencies({Environment environment = cat}) {<br>  <em>//we init the dependencies using a speciffic env<br>  </em>$initGetIt(getIt, environment: environment.<strong>name</strong>);<br>}</pre><p><strong>The config classes:</strong></p><pre>@Environment(<strong>&#39;cat&#39;</strong>)<br>@Injectable(as: IConfig)<br><strong>class </strong>AppConfig <strong>extends </strong>IConfig {<br>  @override<br>  String <strong>get baseUrl </strong>=&gt; <strong>&quot;https://catfact.ninja/&quot;</strong>;<br><br>  @override<br>  Map&lt;String, String&gt; <strong>get headers </strong>=&gt; {};<br><br>  @override<br>  String <strong>get path </strong>=&gt; <strong>&#39;fact&#39;</strong>;<br>}<br><br>@Environment(<strong>&#39;chuck&#39;</strong>)<br>@Injectable(as: IConfig)<br><strong>class </strong>ChuckConfig <strong>extends </strong>IConfig {<br>  @override<br>  String <strong>get baseUrl </strong>=&gt; <strong>&quot;https://api.chucknorris.io/&quot;</strong>;<br><br>  @override<br>  Map&lt;String, String&gt; <strong>get headers </strong>=&gt; {};<br><br>  @override<br>  String <strong>get path </strong>=&gt; <strong>&#39;jokes/random&#39;</strong>;<br>}</pre><p><strong>The fact clients become:</strong></p><pre>@Injectable(as: FactClient)<br>@Environment(<strong>&#39;cat&#39;</strong>)<br><strong>class </strong>CatFactClient <strong>extends </strong>FactClient {<br>  <strong>final </strong>Dio <strong>dio</strong>;<br>  <strong>final </strong>IConfig <strong>config</strong>;<br>  <strong>final </strong>RestClient <strong>client</strong>;<br><br>  @override<br>  Future&lt;Fact&gt; randomFact() {<br>    <strong>return client<br>        </strong>.randomFact(<strong>config</strong>.<strong>path</strong>)<br>        .then((value) =&gt; _fromResponse(value.<strong>rawResponse</strong>));<br>  }<br><br>  Fact _fromResponse(Map&lt;String, <strong>dynamic</strong>&gt; json) {<br>    <strong>return </strong>Fact.fromJson(json);<br>  }<br><br>  CatFactClient({<br>    <strong>required this</strong>.<strong>dio</strong>,<br>    <strong>required this</strong>.<strong>config</strong>,<br>  }) : <strong>client </strong>= RestClient(dio, baseUrl: config.<strong>baseUrl</strong>);<br>}</pre><pre>@Environment(<strong>&#39;chuck&#39;</strong>) // declaring the env<br>@Injectable(as: FactClient)<br><strong>class </strong>ChuckFactClient <strong>extends </strong>CatFactClient {<br>  ChuckFactClient({<br>    <strong>required </strong>Dio dio,<br>    <strong>required </strong>IConfig config,<br>  }) : <strong>super</strong>(dio: dio, config: config);<br><br>  @override<br>  Fact _fromResponse(Map&lt;String, <strong>dynamic</strong>&gt; json) {<br>    String data = <strong>&quot;</strong>${json[<strong>&#39;value&#39;</strong>]}<strong>&quot;</strong>;<br>    <strong>return </strong>Fact(data, data.<strong>length</strong>);<br>  }<br>}</pre><p>And in <em>main.dart </em>we can set the env which will be used to init the dependencies:</p><pre><strong>void </strong>main() {<br>  WidgetsFlutterBinding.<em>ensureInitialized</em>();<br>  <em>// configureDependencies(environment: cat); so we can get cats<br>  </em>configureDependencies(environment: chuck); <em>// we will get chucks :)<br>  </em>runApp(<strong>const </strong>MyApp());<br>}</pre><p><strong>Important:</strong> don’t forget to invoke <em>Jeff </em>:).</p><p>After these minor changes we can have different env and the injector will pick the right dependency.</p><h3><strong>One more feature and I’m done: <em>Auto registering</em></strong></h3><p>Because it’s a cool library, injectable can auto register all the dependencies by itself, you just have to create a <em>build.yaml</em> file in the same folder with the <em>pubspec.yaml. </em>In this file you can use regex pattern to specify the class names patter for which you want to be auto registered.</p><p>Now, I won’t use this feature in the near feature, not because it’s not working, it’s simply because i have a lot of typos in my class names that i tend to refactor when autocomplete can’t find what i type (i know, i know….that’s bad) and i don’t want to spend time figuring out why the lib doesn’t inject my class.</p><h3>Short summary:</h3><ul><li>you can provide a dispose method for your singleton annotation to release any resources that you don’t need</li><li>using <em>inject(as:) </em>with <em>named </em>annotation you can provide multiple implementations for the same abstract class</li><li>using <em>environment </em>annotation, you can inject based on your environment</li><li>you can auto register all dependencies using a config file.</li></ul><h3><strong>Developer notes:</strong></h3><ul><li>it’s super important to run the build_runner every time you change something that will affect the generation process. Make a habit out of it, it will save you a ton of time</li><li>it appears that <em>named and environment</em> annotation may cause problems on the autogenerated code, i’ll give it a try and see what’s the result.</li></ul><h3>Q&amp;A</h3><ol><li><strong>This DI, doesn’t feel to complicated already ?</strong></li></ol><p>I would say, there are some things that you need to get used with, but after the first couple of classes that you configure, you’ll see how easy and helpful it is.</p><p><strong>2. Your app is simple, it was easy to “refactor” everything, but what about my new huge project, i can’t re-factor everything?</strong></p><p>I would suggest, that everything except the UI should be created around abstract classes, that’s where the power of injectable comes in and that’s how you can easily update your code.</p><p><strong>3. Is there anything left to lear about the injectable lib?</strong></p><p>There are some other features that might interest you but I think i covered the most important ones so far.</p><p><strong>One more thing before you go:</strong></p><p>You can find the code on my Github at:</p><p><a href="https://github.com/danypata/dependency_injection_advanced.git">GitHub - danypata/dependency_injection_advanced: A small example of advanced usage of DI with injector and getit</a></p><p>In case you missed the intro for DI:</p><p><a href="https://medium.com/@dan.patacean/flutter-stories-dependency-injection-with-injectable-and-getit-fb95ee7758df">Flutter Stories — Dependency Injection with injectable and getIt</a></p><p>The ⭐️️️ libraries of this post:</p><p><a href="https://pub.dev/packages/injectable">https://pub.dev/packages/injectable</a></p><p><a href="https://pub.dev/packages/get_it">https://pub.dev/packages/get_it</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1d71d21dea01" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Flutter Stories — Dependency Injection with injectable and getIt]]></title>
            <link>https://medium.com/@dan.patacean/flutter-stories-dependency-injection-with-injectable-and-getit-fb95ee7758df?source=rss-9432c7746a3c------2</link>
            <guid isPermaLink="false">https://medium.com/p/fb95ee7758df</guid>
            <category><![CDATA[flutter-tips-and-tricks]]></category>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[dependency-injection]]></category>
            <category><![CDATA[injectable]]></category>
            <category><![CDATA[get-it]]></category>
            <dc:creator><![CDATA[Dan Patacean]]></dc:creator>
            <pubDate>Thu, 08 Sep 2022 09:59:43 GMT</pubDate>
            <atom:updated>2022-09-08T10:00:49.677Z</atom:updated>
            <content:encoded><![CDATA[<h3>Flutter Stories — Dependency Injection with injectable and getIt</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tEqb13jNyjFDYZqrZuWG9Q.png" /><figcaption>Artwork by Roxana P.</figcaption></figure><p>If you landed on this page, you might be:</p><ol><li>New Flutter developer that’s hearing about dependency injection left and right and you decided to give it a try but, you don’t know where to start. I will call you <strong>_dev1.</strong></li><li>A Flutter developer that didn’t used dependency injection so far and now you’re willing to give it a try if it’s not to complicated. You will be <strong>_dev2.</strong></li><li>A well versed developer, that knows all about dependency injection and you want to see if in this article the dependency injection is used correctly. Obviously, this is <strong>_dev3.</strong></li></ol><p>Ok, so what’s actually <strong>dependency injection</strong> ? Well, Wikipedia says that:</p><blockquote><strong><em>Dependency injection</em></strong><em> is a design pattern in which an object or function receives other objects or functions that it depends on. A form of inversion of control, dependency injection aims to separate the concerns of constructing objects and using them, leading to loosely coupled programs. The pattern ensures that an object or function which wants to use a given service should not have to know how to construct those services. Instead, the receiving ‘client’ (object or function) is provided with its dependencies by external code (an ‘injector’), which it is not aware of.</em></blockquote><p>The theory sounds good but how do we translate it in our apps, <strong>_dev1 </strong>would say?</p><p>To answer his question, let’s create a simple app that, let’s say it will load random cat facts from the mighty web.</p><p>For this app, we will need some implementation for the networking part, something to call and maybe validate the data we get and another implementation that will display the fact to the user.</p><h3><strong>First off, dependencies</strong></h3><pre>dependencies:<br>  flutter:<br>    sdk: flutter<br>  # this will be used in combination with injectable for DI  <br>  get_it: ^7.2.0<br>  <br>  #well know library for BLoC state management<br>  flutter_bloc: ^8.1.1<br>  <br>  # use it to generate the code for DI<br>  injectable: ^1.5.3<br>  <br>  # parse api response<br>  json_serializable: ^6.3.1<br>  <br>  #because we are cool<br>  freezed: ^2.0.4<br>  <br>  #because we are extra cool<br>  retrofit: ^3.0.1+1 <br><br>dev_dependencies:<br>  flutter_test:<br>    sdk: flutter<br>  #find problems in our code, because we&#39;re not perfect  <br>  flutter_lints: ^1.0.0<br><br>  #generate the code for networking<br>  retrofit_generator: &#39;&gt;=4.0.0 &lt;5.0.0&#39;<br>  <br>  #generate the code for DI<br>  injectable_generator: 1.5.4<br><br>  #you know this one<br>  build_runner: 2.2.0</pre><p><strong>Networking implementation</strong></p><p>For the networking implementation we will create a simple injectable configuration that will provide the URL of the API and headers in case we need it.</p><pre><strong>import &#39;package:injectable/injectable.dart&#39;</strong>;<br><br><strong>abstract class </strong>IConfig {<br>  String <strong>get baseUrl</strong>;<br><br>  Map&lt;String, String&gt; <strong>get headers</strong>;<br>}<br><br>@Injectable(as: IConfig)<br><strong>class </strong>AppConfig <strong>extends </strong>IConfig {<br>  @override<br>  String <strong>get baseUrl </strong>=&gt; <strong>&quot;https://catfact.ninja/&quot;</strong>;<br><br>  @override<br>  Map&lt;String, String&gt; <strong>get headers </strong>=&gt; {};<br>}</pre><p>We use the abstract class <strong>IConfig</strong> in case we want to provide a different configuration.</p><p>Since we are using retrofit as our networking library, we need to provide a Dio object to our REST client, but because Dio object is not injectable, we have to make it in injectable, for that, <em>injectable </em>lib has the <em>@module annotation.</em></p><p>We are also passing the config to set the global headers to Dio.</p><pre><strong>import &#39;package:dependency_injection/networking/config.dart&#39;</strong>;<br><strong>import &#39;package:dio/dio.dart&#39;</strong>;<br><strong>import &#39;package:injectable/injectable.dart&#39;</strong>;<br><br>@module<br><strong>abstract class </strong>DioProvider {<br>  @singleton<br>  Dio dio(IConfig config) {<br>    Dio dio = Dio();<br>    dio.<strong>options</strong>.<strong>headers </strong>= config.<strong>headers</strong>;<br>    <strong>return </strong>dio;<br>  }<br>}</pre><p>Let’s define our simple API using retrofit.</p><pre><strong>import &#39;package:dependency_injection/networking/models/fact.dart&#39;</strong>;<br><strong>import &#39;package:dio/dio.dart&#39;</strong>;<br><strong>import &#39;package:retrofit/retrofit.dart&#39;</strong>;<br><br><strong>part &#39;rest_client.g.dart&#39;</strong>;<br><br>@RestApi(baseUrl: <strong>&quot;https://catfact.ninja/&quot;</strong>)<br><strong>abstract class </strong>RestClient {<br>  <strong>factory </strong>RestClient(Dio dio, {String baseUrl}) = _RestClient;<br><br>  @GET(<strong>&quot;/fact&quot;</strong>)<br>  Future&lt;Fact&gt; randomFact();<br>}</pre><p>Now let’s add the networking client code:</p><pre><strong>import &#39;package:dependency_injection/networking/config.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/networking/models/fact.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/networking/rest_client.dart&#39;</strong>;<br><strong>import &#39;package:dio/dio.dart&#39;</strong>;<br><strong>import &#39;package:injectable/injectable.dart&#39;</strong>;<br><br><strong>abstract class </strong>FactClient {<br>  Future&lt;Fact&gt; randomFact();<br>}<br><br>@Injectable(as: FactClient)<br><strong>class </strong>CatFactClient <strong>extends </strong>FactClient {<br>  <strong>final </strong>Dio <strong>dio</strong>;<br>  <strong>final </strong>IConfig <strong>config</strong>;<br>  <strong>final </strong>RestClient <strong>client</strong>;<br><br>  @override<br>  Future&lt;Fact&gt; randomFact() {<br>    <strong>return client</strong>.randomFact();<br>  }<br><br>  CatFactClient({<br>    <strong>required this</strong>.<strong>dio</strong>,<br>    <strong>required this</strong>.<strong>config</strong>,<br>  }) : <strong>client </strong>= RestClient(dio, baseUrl: config.<strong>baseUrl</strong>);<br>}</pre><p>Again we are using an abstract class, in case we might want to have a different implementation for the FactClient, that can be easily injected.</p><p>Now the networking response model</p><pre><strong>import &#39;package:json_annotation/json_annotation.dart&#39;</strong>;<br><br><strong>part &#39;fact.g.dart&#39;</strong>;<br><br>@JsonSerializable(fieldRename: FieldRename.<strong>snake</strong>, explicitToJson: <strong>true</strong>)<br><strong>class </strong>Fact {<br>  <strong>final </strong>String <strong>fact</strong>;<br>  <strong>final </strong>int <strong>length</strong>;<br><br>  <strong>factory </strong>Fact.fromJson(Map&lt;String, <strong>dynamic</strong>&gt; json) {<br>    <strong>return </strong>_$FactFromJson(json);<br>  }<br><br>  Fact(<strong>this</strong>.<strong>fact</strong>, <strong>this</strong>.<strong>length</strong>);<br><br>  Map&lt;String, <strong>dynamic</strong>&gt; toJson() =&gt; _$FactToJson(<strong>this</strong>);<br>}</pre><p>And one more crucial piece of code, the injector:</p><pre><strong>import &#39;package:dependency_injection/di/injector.config.dart&#39;</strong>;<br><strong>import &#39;package:get_it/get_it.dart&#39;</strong>;<br><strong>import &#39;package:injectable/injectable.dart&#39;</strong>;<br><br><strong>final </strong>getIt = GetIt.<em>instance</em>;<br><br>@InjectableInit(<br>  initializerName: <strong>r&#39;$initGetIt&#39;</strong>, <em>// the default method name that will be generated<br>  </em>preferRelativeImports: <strong>true</strong>, <em>// default<br>  </em>asExtension: <strong>false</strong>, <em>// default<br></em>)<br><strong>void </strong>configureDependencies() {<br>  $initGetIt(getIt);<br>}</pre><p>The injector will be used to provide the dependencies everywhere in our application by injecting them.</p><p><strong>Make sure to call the <em>configureDependencies()</em> in your main file like so:</strong></p><pre><strong>void </strong>main() {<br>  WidgetsFlutterBinding.<em>ensureInitialized</em>();<br>  configureDependencies();<br>  runApp(<strong>const </strong>MyApp());<br>}</pre><p>We’re done with the code but we don’t have the generated code, for that, you guest it:</p><p><em>flutter pub run build_runner build — delete-conflicting-outputs</em></p><p>Please remember to run this command, every time you do changes in a file that has injectable annotation.</p><blockquote><strong>Pro tip (for _dev1)</strong>: you can create an alias for the build_runner command, mine is called jeff.</blockquote><blockquote>alias jeff=’flutter pub run build_runner build — delete-conflicting-outputs’</blockquote><p>Ok, now we have the networking implementation in place, it’s time to test it.</p><pre><strong>import &#39;package:dependency_injection/di/injector.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/networking/fact_client.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/networking/models/fact.dart&#39;</strong>;<br><strong>import &#39;package:flutter_test/flutter_test.dart&#39;</strong>;<br><br><strong>void </strong>main() {<br>  setUpAll(() {<br>    configureDependencies();<br>  });<br><br>  test(<strong>&#39;we should be able to do an api call&#39;</strong>, () <strong>async </strong>{<br>    FactClient client = getIt&lt;FactClient&gt;();<br><br>    Fact fact = <strong>await </strong>client.randomFact();<br><br>    expect(fact, isNotNull);<br>    expect(fact.<strong>fact</strong>, isNotNull);<br>  });<br>}</pre><p>And the test will pass, you have to believe me, or you have to write all this code. If your test doesn’t pass, well something is wrong with your code :)).</p><p><strong>Now let’s create a simple repo</strong></p><p>For this app, the repo is not required but it’s good practice to keep your architecture even for small apps.</p><pre><strong>import &#39;package:dependency_injection/networking/fact_client.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/networking/models/fact.dart&#39;</strong>;<br><strong>import &#39;package:injectable/injectable.dart&#39;</strong>;<br><br><strong>abstract class </strong>FunFactRepo {<br>  Future&lt;Fact&gt; oneMoreFactPlease();<br>}<br><br>@Injectable(as: FunFactRepo)<br><strong>class </strong>CatFacts <strong>extends </strong>FunFactRepo {<br>  <strong>final </strong>FactClient <strong>factClient</strong>;<br><br>  CatFacts({<br>    <strong>required this</strong>.<strong>factClient</strong>,<br>  });<br><br>  @override<br>  Future&lt;Fact&gt; oneMoreFactPlease() <strong>async </strong>{<br>    <strong>try </strong>{<br>      <strong>return await factClient</strong>.randomFact();<br>    } <strong>catch </strong>(e) {<br>      <strong>return </strong>Fact(<strong>&quot;The fact is you have an error: </strong>${e.toString()}<strong>&quot;</strong>, -1);<br>    }<br>  }<br>}</pre><p>Let’s call <strong>Jeff</strong> aka <em>flutter pub run build_runner build — delete-conflicting-outputs </em>one more time.</p><h3><strong>Let’s do the BLoC now:</strong></h3><p>The main BLoC</p><pre><strong>import &#39;package:bloc/bloc.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/blocs/home_event.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/blocs/home_state.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/networking/models/fact.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/repos/simple_repo.dart&#39;</strong>;<br><strong>import &#39;package:injectable/injectable.dart&#39;</strong>;<br><br>@injectable<br><strong>class </strong>HomeBloc <strong>extends </strong>Bloc&lt;HomeEvent, HomeState&gt; {<br>  <strong>final </strong>FunFactRepo <strong>factRepo</strong>;<br><br>  HomeBloc(<strong>this</strong>.<strong>factRepo</strong>) : <strong>super</strong>(HomeState.nada()) {<br>    on&lt;HomeEvent&gt;((event, emit) {<br>      event.when(oneMoreFunFact: () <strong>async </strong>{<br>        emit(HomeState.loading());<br>        Fact fact = <strong>await factRepo</strong>.oneMoreFactPlease();<br>        <strong>if </strong>(fact.<strong>length </strong>== -1) {<br>          <em>// the most safe way to check for error eva<br>          </em>emit(HomeState.error(fact.<strong>fact</strong>));<br>        } <strong>else </strong>{<br>          emit(HomeState.funFact(fact));<br>        }<br>      });<br>    });<br>  }<br>}</pre><p>The state and event using freeze because we are so cool devs. You too <strong>_dev1.</strong></p><pre><strong>import &#39;package:dependency_injection/networking/models/fact.dart&#39;</strong>;<br><strong>import &#39;package:freezed_annotation/freezed_annotation.dart&#39;</strong>;<br><br><strong>part &#39;home_state.freezed.dart&#39;</strong>;<br><br>@freezed<br><strong>class </strong>HomeState <strong>with </strong>_$HomeState {<br>  <strong>factory </strong>HomeState.nada() = Nada;<br><br>  <strong>factory </strong>HomeState.loading() = Loading;<br><br>  <strong>factory </strong>HomeState.funFact(Fact fact) = FunFact;<br><br>  <strong>factory </strong>HomeState.error(String error) = Error;<br>}</pre><pre><strong>import &#39;package:freezed_annotation/freezed_annotation.dart&#39;</strong>;<br><br><strong>part &#39;home_event.freezed.dart&#39;</strong>;<br><br>@freezed<br><strong>class </strong>HomeEvent <strong>with </strong>_$HomeEvent {<br><br>  <strong>factory </strong>HomeEvent.oneMoreFunFact() = OneMorePlease;<br>}</pre><p>Let’s call Jeff one more time.</p><p>By now we have all the backbone of our app, we just need to add the UI, something simple, a text and a button to get a new fact. Oh, and maybe a loading indicator while we get the fact, because you know it, we are soo cool. We’ll keep the error handling for <strong>_dev3, </strong>he’s the best of us so far.</p><h3><strong>Our wonderful UI</strong></h3><pre><strong>import &#39;package:dependency_injection/blocs/home_bloc.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/blocs/home_event.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/blocs/home_state.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/di/injector.dart&#39;</strong>;<br><strong>import &#39;package:dependency_injection/networking/models/fact.dart&#39;</strong>;<br><strong>import &#39;package:flutter/material.dart&#39;</strong>;<br><strong>import &#39;package:flutter_bloc/flutter_bloc.dart&#39;</strong>;<br><br><strong>class </strong>HomeScreen <strong>extends </strong>StatefulWidget {<br>  <strong>const </strong>HomeScreen({Key? key}) : <strong>super</strong>(key: key);<br><br>  @override<br>  State&lt;HomeScreen&gt; createState() =&gt; _HomeScreenState();<br>}<br><br><strong>class </strong>_HomeScreenState <strong>extends </strong>State&lt;HomeScreen&gt; {<br>  @override<br>  Widget build(BuildContext context) {<br>    <strong>return </strong>Scaffold(<br>      body: BlocProvider(<br>        create: (_) =&gt; getIt&lt;HomeBloc&gt;(),<br>        child: SafeArea(<br>          child: BlocBuilder&lt;HomeBloc, HomeState&gt;(<br>            builder: (context, state) {<br>              <strong>return </strong>state.when(nada: () {<br>                <strong>return </strong>Center(<br>                  child: mainButton(context),<br>                );<br>              }, loading: () {<br>                <strong>return _loading</strong>;<br>              }, funFact: (fact) {<br>                <strong>return </strong>_factUI(fact, context);<br>              }, error: (error) {<br>                <strong>return </strong>_error(error);<br>              });<br>            },<br>          ),<br>        ),<br>      ),<br>    );<br>  }<br><br>  Center _error(String error) {<br>    <strong>return </strong>Center(<br>      child: Text(error),<br>    );<br>  }<br><br>  Widget _factUI(Fact fact, BuildContext context) {<br>    <strong>return </strong>Padding(<br>      padding: <strong>const </strong>EdgeInsets.all(20),<br>      child: Column(<br>        crossAxisAlignment: CrossAxisAlignment.<strong>start</strong>,<br>        children: [<br>          Text(fact.<strong>fact</strong>),<br>          <strong>const </strong>SizedBox(<br>            height: 10,<br>          ),<br>          Text(<br>            <strong>&#39;Fun fact, the abve fact has: </strong>${fact.<strong>length</strong>}<strong> characters&#39;</strong>,<br>          ),<br>          <strong>const </strong>SizedBox(<br>            height: 15,<br>          ),<br>          mainButton(context),<br>        ],<br>      ),<br>    );<br>  }<br><br>  Widget <strong>get _loading </strong>=&gt; <strong>const </strong>Center(<br>        child: CircularProgressIndicator(),<br>      );<br><br>  Widget mainButton(BuildContext context) {<br>    <strong>return </strong>ElevatedButton(<br>        onPressed: () {<br>          context.read&lt;HomeBloc&gt;().add(HomeEvent.oneMoreFunFact());<br>        },<br>        child: <strong>const </strong>Text(<strong>&quot;Hit me baby one more time&quot;</strong>));<br>  }<br>}</pre><p>Hit run and enjoy your cat fact app. Yeah, it’s a cat fact app…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oZEDU2eQ7Rav9DtNfqTDog.gif" /></figure><p>If you check the code, all our dependencies are passed trough constructor, when you create a new instance of a class, you know exactly what you have to provide to it in order to be created, and the class that needs the dependency doesn’t know anything about it’s creation, it just <strong>getIt </strong>(see what i did there).</p><p>What’s even better, we can actually use the implementation without any library, in that case, we will have to instantiate on our own the dependencies and provide them when we create new objects.</p><h3><strong>Short summary:</strong></h3><ul><li><em>injectable </em>library is a powerful tool that will help you in the dependency injection world.</li><li>use @injectable to mark a class as injectable</li><li>use @module to inject 3rd party library instances</li><li>use @Inject(as:) to provide a solid implementation for an abstract class.</li></ul><h3><strong>Developer notes:</strong></h3><ul><li>For <strong>_dev1, </strong>you may wonder why are we doing al of this crazy stuff, when we could just ad 3 classes, do an async request and boom, app works and it’s done. Well, besides all the best practices and things that are around there, you’ll find out one day, that you’ll have to use unit tests to cover your code, and then you’ll see why you want to separate the implementation. Moreover, one day, your beloved client will tell you that, nope, now i want dog facts, and i want it done by tomorrow, and you’ll be smiling knowing that you just have to change a few pieces of your code and it will magically work.</li><li>For <strong>_dev2, </strong>i would say, injectable lib and getit are cool and you don’t have to use them, but i would strongly advise you to use dependency injection in your future apps.</li><li>For _<strong>dev3,</strong> thank you for staying with me this long, you know all, i don’t have anything to say to you.</li></ul><h3>Q&amp;A form _dev1 &amp; _dev2</h3><ol><li><strong>_dev1: Why should i ever use dependency injection pattern ?</strong></li></ol><p>In my experience, DI pattern, will decouple your code. You will have no problem covering your implementation with tests and in my opinion the implementation will be more clean and clear.</p><p><strong>2. _dev1: Do i have to use injectable and getIt for dependency injection in Flutter?</strong></p><p>Of course not, DI is a design pattern, and a design pattern can be implemented in different ways. Injectable and getIt will reduce the amount of boilerplate code you have to write and it will take care of the construction and injection of the dependencies.</p><p><strong>3. _dev2: The example is a simple app, what if I have a huge app that i need to build and i also have to configure different environments and settings ?</strong></p><p>I think that DI with injectable and getIt can be used on any scale. On the configuration side, I will cover it in a different article that will come up soon.</p><p><strong>4. _dev 2: Do i have to start the project with DI or i can integrate it already in my codebase ?</strong></p><p>In my opinion both ways can work with a minor caveat on the second part, if your base code is highly coupled, you’ll have a bit of a hard time refactoring it, but at the end, you’ll get a high quality code base, testable and scalable. You can start with smaller pieces of your code then work your way trough the whole project.</p><p><strong>One more thing before you go:</strong></p><p>You can find all the code on my Github at:</p><p><a href="https://github.com/danypata/dependency_injection">GitHub - danypata/dependency_injection: a fun app to show off DI.</a></p><p>Stay tuned for the next article where we will do some advanced injections with injectable and getIt.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fb95ee7758df" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>