<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Olivier Revial</title>
    <description>The latest articles on DEV Community by Olivier Revial (@pommedouze).</description>
    <link>https://dev.to/pommedouze</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F399496%2F418e7aa8-88db-4ef0-8d17-3d24a9c1c790.jpeg</url>
      <title>DEV Community: Olivier Revial</title>
      <link>https://dev.to/pommedouze</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pommedouze"/>
    <language>en</language>
    <item>
      <title>Flutter Vikings - Day 2</title>
      <dc:creator>Olivier Revial</dc:creator>
      <pubDate>Fri, 02 Sep 2022 09:46:00 +0000</pubDate>
      <link>https://dev.to/pommedouze/flutter-vikings-day-2-3k8a</link>
      <guid>https://dev.to/pommedouze/flutter-vikings-day-2-3k8a</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0kUHwKri--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uv5xct1fa6a5zydltwo0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0kUHwKri--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uv5xct1fa6a5zydltwo0.jpg" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a great first day of Flutter Vikings conference in Oslo, let's dive into the second day with the conferences I have seen ! &lt;/p&gt;

&lt;h2&gt;
  
  
  The cookbook of Flutter testing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1hLBRle1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iyh0tdp31isc8mn4xf62.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1hLBRle1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iyh0tdp31isc8mn4xf62.jpg" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/teresa_wyy"&gt;Teresa Wu&lt;/a&gt; started this talk by stating that the might important thing about test might not be a very high code coverage (or at least that aiming for it was not absolutely necessary) but that instead we should aim at doing proper testing. &lt;/p&gt;

&lt;p&gt;She then described the different kinds of tests that we can think about in an app :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Behaviour Driven Testing (BDD) with TDD&lt;/li&gt;
&lt;li&gt;Unit tests (for data and domain)&lt;/li&gt;
&lt;li&gt;Acceptance testing (for domain)&lt;/li&gt;
&lt;li&gt;End-to-end and snapshot testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;She particularly insisted on BDD testing in this talk as it might be the less well-known and yet a very important one. Different steps are BDD were described as follows :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Discovery story&lt;/li&gt;
&lt;li&gt;Backlog / User acceptance&lt;/li&gt;
&lt;li&gt;Example mapping (small meetings to ask all the questions, trying to think about the edge cases, particular scenarios such as failing cases, ...) &lt;/li&gt;
&lt;li&gt;Gherkin scenario, using a gherkin package in Flutter to actually write the tests in a spoken language&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a small example of example mapping :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_-PM8Jwm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d4jt8v2sz5vtq2r794d6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_-PM8Jwm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d4jt8v2sz5vtq2r794d6.jpg" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To end this talk Teresa talked about widget testing, integration testing and also contract testing to make sure APIs clients behave as expected. &lt;/p&gt;

&lt;h2&gt;
  
  
  Parallel Beauty with Isolates into the Mandelbrot set
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--foy8-sB6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zxcx6o61d9tet6iqqs1t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--foy8-sB6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zxcx6o61d9tet6iqqs1t.jpg" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Despite a lot of technical difficulties during the talk, &lt;a href="https://twitter.com/ThomasBurkhartB"&gt;Thomas Burkhart&lt;/a&gt; still did a great presentation. He first introduced how Dart is multi-threaded even though we tend to think of Dart as a single-threaded language because we usually only use the main thread in an app. &lt;br&gt;
He also explained the differences between single-threading, multi-threading and parallel processing. &lt;/p&gt;

&lt;p&gt;After this useful introduction it was time to dive in the code to learn more about Dart Isolates. Although compute() is great to create small isolates, Thomas showed us in great details how you can manually handle isolates : message streams with send and receive ports, how global variables, static variables and parameters are always passed as copies, how to handle a pool of isolates... Very technical and very instructive too, we definitely learnt a lot! &lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Flutter apps in Lego style
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--chPRuE7Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m67x8y5odptaw1imj8e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--chPRuE7Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m67x8y5odptaw1imj8e.jpg" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This title was definitely catchy, I had to see what it was really about! &lt;/p&gt;

&lt;p&gt;In this recording talk by &lt;a href="https://twitter.com/AnnaLeushchenko"&gt;Anna&lt;/a&gt; and &lt;a href="https://twitter.com/OlexaLe"&gt;Oleksander Leushchenko&lt;/a&gt; (unfortunaly Oleksander could not join in person because of the situation going on in Ukraine 🇺🇦 😢), they started by explaining how Lego is so powerful and why you can still today arrange 1970 bricks with 2022 bricks : extreme modularity.&lt;/p&gt;

&lt;p&gt;Although code might not be able to reach such a level of modularity, modular programming exists and we might try to use it. &lt;/p&gt;

&lt;p&gt;Basically modularity in Dart resides in making modules (duh! ) aka packages (at least to keep things simple), and all the code needs to rely on interfaces. &lt;/p&gt;

&lt;p&gt;Anna and Oleksander then started to explain how we can use (de)composition to achieve greater modularity in our code. The first step is to separate our code in three main parts :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App &lt;/li&gt;
&lt;li&gt;Features&lt;/li&gt;
&lt;li&gt;Core&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each feature will basically be in its own package, core will be for common things such as design system and app will be here to connect all the features together, handle external navigation and such. &lt;/p&gt;

&lt;p&gt;They then had a dive at the feature part which is probably where most of the code will be written. Features can be further decomposed into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain, with models and services&lt;/li&gt;
&lt;li&gt;Dependency Injection&lt;/li&gt;
&lt;li&gt;Configuration, with either local feature flags, remote feature flags or flavors&lt;/li&gt;
&lt;li&gt;Localization, that can either be set through common i18n or through feature-local i18n&lt;/li&gt;
&lt;li&gt;UI and state management for only feature-specific parts. As stated previously shared widgets and design will come from the core package&lt;/li&gt;
&lt;li&gt;Navigation, for the feature-internal part. External navigation will be handled later by the app package&lt;/li&gt;
&lt;li&gt;Tests. As features are very isolated from each other, they are easy to test and should probably tested with BDD (using Gherkin), testing only the entrypoint of the feature as the rest will be called by the feature and is implementation details. Also greater isolation allows for easier testing because the feature can be tested almost without mocks, just adding HTTP interceptors to mock API / backend. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally Anna and Oleksander presented various tools to help handling the number of packages, such as Mason to write templates for easier feature package generation and Melos for the "orchestration" part (compiling in a certain order and such).&lt;/p&gt;

&lt;h2&gt;
  
  
  Building better Bingo backends
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r0Csr-lb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9aqvb0gbzqpzsakeahjy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r0Csr-lb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9aqvb0gbzqpzsakeahjy.jpg" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this very fun talk, &lt;a href="https://twitter.com/puf"&gt;Frank Van Puffelen&lt;/a&gt; and Eric Windmill from the Firebase team took Bingo as an example app for setting a Firestore database (and only that) to create a multi-player app with both clients and host. &lt;/p&gt;

&lt;p&gt;They went through the basics of Firestore : how it is document-structured, how it is real-time and you can get snapshots updates as soon as there are changes in the database, how you can use security rules to secure database access (because obvisouly you don't want your clients to be able to touch to the host part) and more features. &lt;/p&gt;

&lt;p&gt;Very fun talk while instructive on Firestore basics! &lt;/p&gt;

&lt;h2&gt;
  
  
  Game Development in Flutter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oR2Ingai--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0epyl0ionaa77zfgy1sd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oR2Ingai--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0epyl0ionaa77zfgy1sd.jpg" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last talk I saw on Day 2 was from &lt;a href="https://twitter.com/filiphracek"&gt;Filip Hracek&lt;/a&gt; where he gave a very honest feedback on Game development when you try to make your own as a personal project (i.e. not working for a big game studio). &lt;/p&gt;

&lt;p&gt;In the first part he asked why we, as developers, initially want to develop a game. He took an interesting parallel with movies using a Scorsese quote. Basically, we want to create game because we can do what we want with it, potentially passing a message to players, by taking much more risks than a big studio. &lt;/p&gt;

&lt;p&gt;He then explained the various reasons why games fail, often because life happens, or it's just too much work and we lack discipline, or we were stuck on a specific aspect of game design, architectural issue or perf issues. The answer to this was basically to know from the beginning that developing a game is work, it's not just fun all the way. &lt;/p&gt;

&lt;p&gt;In the final part he gave some advices to avoid problems stated before. It's okay if it's not beautiful from the very beginning, it's okay if it feels hacky (after all the players don't care!), you can start small and don't need everything done at first. Also, don't reinvent the wheel and finally : try to be your own Product Manager, you have to set boundaries to yourself! &lt;/p&gt;

&lt;p&gt;After Filip talk we were close to the end of the Day, but there was a great Q&amp;amp;A session with Dart, Flutter and Firebase teams that answers attendees questions. A great way to get honest answers for the future of Flutter, Dart and Fireb&lt;/p&gt;

&lt;p&gt;I must say that it was a great way to end this awesome Flutter Vikings conference. &lt;/p&gt;

&lt;p&gt;The end.&lt;/p&gt;

&lt;p&gt;...until next year ? 🤞&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
    </item>
    <item>
      <title>Flutter Vikings - Day 1</title>
      <dc:creator>Olivier Revial</dc:creator>
      <pubDate>Wed, 31 Aug 2022 16:32:49 +0000</pubDate>
      <link>https://dev.to/stack-labs/flutter-vikings-day-1-13fc</link>
      <guid>https://dev.to/stack-labs/flutter-vikings-day-1-13fc</guid>
      <description>&lt;p&gt;This year I have the priviledge of attending Flutter Vikings, the big european conference about Dart and Flutter. &lt;/p&gt;

&lt;p&gt;Before I start talking about the great talks I have seen on Day 1, let me say that Flutter team teased a new Flutter version the day before the event : Flutter 3.3. Here is &lt;a href="https://medium.com/flutter/announcing-flutter-3-3-at-flutter-vikings-6f213e068793" rel="noopener noreferrer"&gt;the announcement&lt;/a&gt; and here is &lt;a href="https://docs.flutter.dev/development/tools/sdk/release-notes/release-notes-3.3.0" rel="noopener noreferrer"&gt;the changelog&lt;/a&gt;. This newest version aims at stabilizing Flutter 3 with lot of useful fixes and a few small features too. Oh and as always, a major Flutter update comes with a Dart update too, today we get Dart 2.18, with no outstanding news thought.&lt;/p&gt;

&lt;p&gt;Now let's jump to the subject : Flutter Vikings Day 1 !&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive into Flutter theming
&lt;/h2&gt;

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

&lt;p&gt;In this very first talk, &lt;a href="https://twitter.com/RydMike" rel="noopener noreferrer"&gt;Mike Rydstron&lt;/a&gt; presented various theme related concepts.&lt;/p&gt;

&lt;p&gt;He started by presenting how to use ThemeData the right way as there are like 8 different ways to declare colors... ThemeData(), ThemeData.from(), ColorScheme(), ColorScheme.fromSeed(),... Curiously, they don't all work in the same way and some of them might miss a few colors that you then have to fix manually. &lt;br&gt;
The talk continued with the current state of Material 3 and where it stands compared to Material 2.&lt;/p&gt;

&lt;p&gt;In the second half of the talk, Mike showed how to create beautiful color schemes and presented a few packages and methods he developed, for example &lt;a href="https://pub.dev/packages/flex_color_scheme" rel="noopener noreferrer"&gt;flex_color_scheme&lt;/a&gt; to add more flexibility to how color schemes currently work in Flutter Material 3 implementation. &lt;/p&gt;

&lt;p&gt;The talk was ended with a demonstration of &lt;a href="https://rydmike.com/flexcolorscheme/themesplayground-v6/" rel="noopener noreferrer"&gt;Theme Playground&lt;/a&gt;, a very nice application to play and generate theme related code! &lt;/p&gt;

&lt;h2&gt;
  
  
  Fun with code generation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://twitter.com/AnnaLeushchenko" rel="noopener noreferrer"&gt;Anna Leucshenko&lt;/a&gt; gave a great talk on how to use code generation with build_runner package to write less boilerplate code. In this talk she used two applications to compare how code generation can simplify an app code. She went through quite a lot of packages which are :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;freezed to generate models with constructors, equals, hash code, copyWith and other awesome features such as pattern matching&lt;/li&gt;
&lt;li&gt;json_serializable to generate toJson() and fromJson() methods&lt;/li&gt;
&lt;li&gt;retrofit for REST APIs&lt;/li&gt;
&lt;li&gt;auto_route for Flutter navigation&lt;/li&gt;
&lt;li&gt;flutter_gen for assets linking (making images accessible through constants instead of specifying a path) &lt;/li&gt;
&lt;li&gt;injectable to use effective dependency injection with get_it&lt;/li&gt;
&lt;li&gt;i69n for localizations&lt;/li&gt;
&lt;li&gt;bdd_widget_test to write behavior driven tests using Gherkin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should definitely check this talk and the packages if you want to learn more about code generation&lt;/p&gt;

&lt;h2&gt;
  
  
  Flutter At Scale - Experience From 25+ Flutter Devs Working Together On A Mobile Banking App
&lt;/h2&gt;

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

&lt;p&gt;This talk was a very interesting feedback of two developers working on a large team of 25 Flutter devs and an overall 200 people team. In such project you need to be pretty organized in order for your codebase not to become a real mess. &lt;/p&gt;

&lt;p&gt;The speakers led us to various ways of dealing with big projects. Code ownership was the first subject as you need to have people responsible for every piece of code and you cannot just say that the team is responsible for everything. &lt;br&gt;
The following points were focusing on the project structure and architecture : organizing the files by feature, having a robust design system, using atomic design to separate the widgets. &lt;/p&gt;

&lt;p&gt;At the end they presented their storybook, a very nice way of showing every component in their different states (e.g. a button that might be enable / disable, hover, etc) &lt;/p&gt;

&lt;h2&gt;
  
  
  Responsive UI for auth flows
&lt;/h2&gt;

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

&lt;p&gt;In this talk &lt;a href="https://twitter.com/salihgueler" rel="noopener noreferrer"&gt;Salih Guler&lt;/a&gt; from AWS Amplify introduced concepts that revolve around adaptative and responsive layouts. The first part of the talk was to explain the difference between those two terms : adaptive is more about adapting the layout, e.g. the small burger menu becoming a left pane on bigger devices, whereas responsive is more about making everything fit within the available space, depending on screen size.&lt;/p&gt;

&lt;p&gt;The second and biggest part of the talk was a very nice live coding where he demonstrated a lot of useful widgets  :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LayoutBuilder&lt;/li&gt;
&lt;li&gt;Media Query.of()&lt;/li&gt;
&lt;li&gt;FractionnallySizedBox&lt;/li&gt;
&lt;li&gt;OrientationBuilder&lt;/li&gt;
&lt;li&gt;FittedBox&lt;/li&gt;
&lt;li&gt;AspectRatio&lt;/li&gt;
&lt;li&gt;CustomMultiChildLayout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And of course the more obvious and known ones such as Row, Column, Flexible, Expanded and so on. &lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Flutter's handling of gestures
&lt;/h2&gt;

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

&lt;p&gt;&lt;a href="https://twitter.com/Guitoof" rel="noopener noreferrer"&gt;Guillaume Diallo-Mulliez&lt;/a&gt; explained in plain details how the gesture detection and handling work in Flutter, and how we should do if we ever wanted to implement our own custom gesture detection or gesture listening.&lt;/p&gt;

&lt;p&gt;This talk is a bit hard to transcribe because it was really technical but Guillaume talked about GestureDetector, RawGestureDetectore, pointers and PointerEvent, gesture Listener, hit tests and so on.&lt;/p&gt;

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

&lt;p&gt;If you are interested I can only encourage you to watch the replay as it will be much more valuable than a text transcription!&lt;/p&gt;

&lt;h2&gt;
  
  
  Riverpod 2.0
&lt;/h2&gt;

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

&lt;p&gt;Last but not least, &lt;a href="https://twitter.com/remi_rousselet" rel="noopener noreferrer"&gt;Rémi Rousselet&lt;/a&gt; - famous author of freezed, provider and riverpod packages - gave us a few hints about what Riverpod 2.0 (one of the most-used state management library out there) could look like in, hopefully, the near future! New state inspector in DevTools for Riverpod, custom linter, new documentation generator with a sweet dependency graph,... Many awesome features!&lt;/p&gt;

&lt;p&gt;But then we had the main and best part of the talk where Rémi did an awesome live coding session showing a potential future step-by-step tutorial for Riverpod using new annotations to infer providers type and simplify overall use of Riverpod for beginners.&lt;/p&gt;

&lt;p&gt;Hopefully it will be available on pub.dev soon because it looked great 🔥&lt;/p&gt;

&lt;p&gt;Anyway, that sums up this awesome first day of Flutter Vikings 2022, see you tomorrow for part 2! &lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
    </item>
    <item>
      <title>Flutter : a journey to higher-quality apps</title>
      <dc:creator>Olivier Revial</dc:creator>
      <pubDate>Tue, 26 Jul 2022 11:21:00 +0000</pubDate>
      <link>https://dev.to/stack-labs/flutter-a-journey-to-higher-quality-apps-1m1a</link>
      <guid>https://dev.to/stack-labs/flutter-a-journey-to-higher-quality-apps-1m1a</guid>
      <description>&lt;p&gt;Ever been scared of pressing that CI "release" button ? Do the words "bugs" and "regressions" ring any bell ?&lt;/p&gt;

&lt;p&gt;It's time to leave the fear behind and start delivering higher-quality apps !&lt;/p&gt;

&lt;p&gt;In this article I will try to show you why code quality is important and how to improve it in your Flutter app through various tools and techniques. Hopefully by the end of this article you will have the keys to deliver your apps with more confidence 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  You said code quality ? 🤔
&lt;/h2&gt;

&lt;p&gt;Before jumping right to the solutions, I think it's important to do a small reminder of what code quality is, and why it is (very very very) important.&lt;/p&gt;

&lt;p&gt;Code quality is a metric that usually tell whether a given code is good or bad. Obviously this is very subjective and differ from one project to another but it doesn't really matter because the goals behind code quality are almost &lt;a href="https://www.parasoft.com/solutions/code-quality" rel="noopener noreferrer"&gt;always the same&lt;/a&gt; :&lt;/p&gt;

&lt;h3&gt;
  
  
  Increasing confidence &amp;amp; reputation 👍
&lt;/h3&gt;

&lt;p&gt;Delivering a software with confidence is extremely important because that way you do not fear new releases and as you deliver with more confidence, you can deliver more often, making your delivery cycle a virtuous circle. And as developers we are always happy not having to work on the week-end because the latest release was full of bugs. 🦟 🪲&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Less risk, more fun.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Obviously, your company reputation is also at stake here : the more perceived software quality (fast, secure, reliable, etc) your users get, the more confidence they will have to trust you and keep using your app (and even recommend it to others).&lt;/p&gt;

&lt;h3&gt;
  
  
  Increasing code reuse ⌨️
&lt;/h3&gt;

&lt;p&gt;Unless you are developing a one-time POC (and even then, beware the &lt;a href="https://blog.bitsrc.io/its-not-going-to-scale-going-from-poc-to-production-ready-e95d51729516" rel="noopener noreferrer"&gt;"POC to production" effect&lt;/a&gt; !), you will spend a lot of time developing and maintaining your app : it could be weeks, months or even years ! The more quality you put at first, the less cost you will have to add further developments in the future.&lt;/p&gt;

&lt;p&gt;In the other hand, low quality code would not only make you pay more in the long term but could really damage your company business as any change to the app could get really tricky and will potentially freeze your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  What metrics make for a good code quality app ? 📊
&lt;/h3&gt;

&lt;p&gt;I won't go into details here because &lt;a href="https://www.perforce.com/blog/sca/what-code-quality-overview-how-improve-code-quality" rel="noopener noreferrer"&gt;other articles&lt;/a&gt; &lt;a href="https://www.parasoft.com/solutions/code-quality/" rel="noopener noreferrer"&gt;already&lt;/a&gt; &lt;a href="https://www.sealights.io/code-quality/code-quality-metrics-is-your-code-any-good/" rel="noopener noreferrer"&gt;describe this&lt;/a&gt;, but basically we can find :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt;. How probable our app will run without unexpected failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability&lt;/strong&gt;, &lt;strong&gt;extensibility&lt;/strong&gt; and &lt;strong&gt;reusability&lt;/strong&gt;. How easy it is to add new code around existing code or change existing code. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability&lt;/strong&gt;. How easy it is to test the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portability&lt;/strong&gt;. How well your app will behave in different environments. For a Flutter mobile app you should make sure that your app is running as well on Android and on iOS. For a web or desktop app, you also might want to test multiple browsers or OS platforms.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code quality in Flutter 📱
&lt;/h2&gt;

&lt;p&gt;Code quality in Flutter is no different from code quality in any other language, framework or anything else in general. Linters, tests, CI, reviews, refactoring, coding conventions, code quality measurement tools...&lt;/p&gt;

&lt;p&gt;Fortunately for us, Flutter has them all !&lt;/p&gt;

&lt;p&gt;I will now show a few tools that can be used to improve, guarantee and measure our code quality in Flutter. Note that my list might not be exhaustive (please add your tools in the comments !), and is in no particular order. Finally, it is important to understand a few things when talking about code quality :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good/experimented developers tend to write better quality code. Do not rely solely on tools below to claim that your code is good, rather think about the code you are writing and try to write the best code you can. Tools are only here to help you, but they won't think for you. This is particularly true for business matters where tools can hardly verify what's actually expected.&lt;/li&gt;
&lt;li&gt;You should not pick a single tool and think that it will be enough to have a good code quality. Many tools should be used together to incrementally improve code quality and measure it. And in the end, do not forget that tools can only do what you want them to do and their usefulness depends on what you do with them. As an example, it's good to have tests but if no one in the team ever look at whether they are succeeding or failing, they are pretty much useless.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Linters ℹ️
&lt;/h3&gt;

&lt;p&gt;Linters are simple code analysis programs that are able to detect common programming errors and syntax issues. The way linters work is by defining a set of rules, that can usually be configured, and those rules will be checked by a command line or by the IDE every time a new code is added/updated/removed.&lt;/p&gt;

&lt;p&gt;The good news is that since Flutter 2.10, the package &lt;a href="https://pub.dev/packages/flutter_lints" rel="noopener noreferrer"&gt;flutter_lints&lt;/a&gt; is added by default to your &lt;code&gt;pubspec.yaml&lt;/code&gt; when you create a new app with Flutter command line &lt;code&gt;flutter create&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dev_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flutter&lt;/span&gt;
  &lt;span class="na"&gt;flutter_lints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^2.0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, you also get a file called &lt;code&gt;analysis_options.yaml&lt;/code&gt; at the root of your project that will include the source package :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;package:flutter_lints/flutter.yaml&lt;/span&gt;

&lt;span class="na"&gt;linter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what do we have here ? Pretty easy, we are just saying that we want to use pre-configured set of rules from &lt;a href="https://pub.dev/packages/flutter_lints" rel="noopener noreferrer"&gt;flutter_lints&lt;/a&gt; package. Joy of open source, you can jump to the actual repository to &lt;a href="https://github.com/flutter/packages/blob/main/packages/flutter_lints/lib/flutter.yaml" rel="noopener noreferrer"&gt;check what rules are defined&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now you have to keep in mind that this set of rules has been defined by the Flutter team as the minimal set of recommended rules. Though we can trust the team that developed the framework to give us good rules, you can always customize this and deactivate or activate individual rules.&lt;/p&gt;

&lt;p&gt;Say I want to enforce the declaration of return types and trailing commas, I just have to update my &lt;code&gt;analysis_options.yaml&lt;/code&gt; file :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;package:flutter_lints/flutter.yaml&lt;/span&gt;

&lt;span class="na"&gt;linter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;always_declare_return_types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;required_trailing_commas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you're curious what rules exists, &lt;a href="https://dart.dev/tools/linter-rules#lints" rel="noopener noreferrer"&gt;the complete list is here&lt;/a&gt; ! In my opinion it's always good to take a look at the existing rules to understand how to better code in Dart and Flutter, and understand what to avoid.&lt;/p&gt;

&lt;p&gt;In my case I usually customize a lot of rules to define code conventions that work for my team. I like to have linter rules by default in new apps but the default linter sets about 44% of rules which is quite lax, I rather have stronger code conventions so everyone in the team uses pretty much the same code, and is able to read others' code. I usually activate around 80% of rules. &lt;/p&gt;

&lt;p&gt;If you want to learn more about Flutter linting including a comparison between various popular packages, you should definitely read this &lt;a href="https://rydmike.com/blog_flutter_linting.html#what-are-the-rule-differences-between-all-these-packages" rel="noopener noreferrer"&gt;Mike Rydstorm article on Flutter Linting&lt;/a&gt;, and he even created a &lt;a href="https://docs.google.com/spreadsheets/d/1Nc1gFjmCOMubWZD7f2E4fLhWN7LYaOE__tsA7bf2NjA/edit" rel="noopener noreferrer"&gt;Google Sheet table to compare the linter packages rule by rule&lt;/a&gt; !&lt;/p&gt;

&lt;p&gt;Finally, to take linters to the next level, have a look at &lt;a href="https://pub.dev/packages/dart_code_metrics" rel="noopener noreferrer"&gt;Dart Code Metrics&lt;/a&gt;. This package takes linter rules a little bit further by checking additional rules, more complex anti-patterns or code metrics, and also by checking for unused files and l10n strings.&lt;/p&gt;

&lt;p&gt;Oh and before I forget, if you are new to Dart language or feel like you need a refresher, you absolutely must have a look at &lt;a href="https://dart.dev/guides/language/effective-dart" rel="noopener noreferrer"&gt;Effective Dart guidelines&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refactoring &amp;amp; IDE features 🧑‍💻
&lt;/h3&gt;

&lt;p&gt;I usually spot a junior developer quickly on the first pair-programming or pair-review session with the way a developer uses its tools. I don't mean to offend anyone, I'm just saying modern IDEs have a lot of tools and features that allow quick and strong refactoring, so we should use and abuse them, that's why there are here for.&lt;/p&gt;

&lt;p&gt;VSCode and AndroidStudio (probably used by 99.99% of Flutter developers nowadays) both include powerful tools to refactor. Whether you want to rename a field or a method, extract a code block to a method or inline a method into another, you probably can delegate this cumbersome task to your IDE:&lt;/p&gt;

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

&lt;p&gt;And if you do this often, learn the shortcut and become more efficient in your everyday life !&lt;/p&gt;

&lt;p&gt;But your favorite IDE has many other (not-so) hidden gems.&lt;/p&gt;

&lt;p&gt;A good example is quickfix that will apply fixes to common mistakes / warnings : &lt;/p&gt;

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

&lt;p&gt;And you know what ? The great thing is that your IDE is able to read your set of defined rules in your &lt;code&gt;analyis_options&lt;/code&gt; file and will then show you the errors / warnings / hints directly in concerned files. How great is that ?&lt;/p&gt;

&lt;p&gt;Another feature that saved my Flutter-developer life when I started is the auto-completion feature. It is just crazy how fast you can create widgets, wrap widgets into others and do a lot of cool things. If you are currently adding widgets and counting how many closing parenthesis you have, just stop and use auto-completion : &lt;/p&gt;

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

&lt;p&gt;You got it, IDEs are powerful and love to work for you, so help yourself and use them. I'll leave you on the IDE part with the auto-format feature, particularly used in Flutter if you don't want to go crazy :&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Tests ✅ ❌
&lt;/h3&gt;

&lt;p&gt;Yes, testing is essential to keep code quality high. It is useful in the design phase because for a component to be testable means that you need to design it correctly (in isolation, should not do too much, etc.). It is also very useful because it is, if done correctly, a repeatable step that can be automated to check for non-regressions as you add features and fix bugs.&lt;/p&gt;

&lt;p&gt;I won't go into too much details on testing because the official documentation already contains a &lt;a href="https://docs.flutter.dev/testing" rel="noopener noreferrer"&gt;comprehensive chapter on testing&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The documentation differentiate three types of tests : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests&lt;/strong&gt; test a single function, method, or class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Widget tests&lt;/strong&gt; (in other UI frameworks referred to as component test) test a single widget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration tests&lt;/strong&gt; test a complete app or a large part of an app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you don't know the difference between those tests, please go read the doc, it is very important to know which does what.&lt;/p&gt;

&lt;p&gt;However I feel that there is a fourth type of test that is often unknown to developers and yet very powerful in my opinion : &lt;/p&gt;

&lt;p&gt;🏆 &lt;strong&gt;Golden tests&lt;/strong&gt; 🏆&lt;/p&gt;

&lt;p&gt;A golden test is just a specific kind of widget test except that we use Flutter test framework to actually check that our widget rendering is conform to what we except it to be. The widget rendering must match a pre-generated rendered image called a "golden file", hence the name of the test.&lt;/p&gt;

&lt;p&gt;I won't go into details here because &lt;a href="https://medium.com/flutter-community/flutter-golden-tests-compare-widgets-with-snapshots-27f83f266cea" rel="noopener noreferrer"&gt;other articles&lt;/a&gt; &lt;a href="https://appunite.com/blog/golden-testing-with-flutter" rel="noopener noreferrer"&gt;already do&lt;/a&gt;, but basically what you need to remember is that golden tests are widget tests, which means they execute almost as fast as widgets tests, and much (much, much) faster than integration tests, and allow for quickly testing visual rendering of our widgets.&lt;/p&gt;

&lt;p&gt;Here is an example where I generated a golden file for my tiles widget, on both light and dark themes (with a single golden test) : &lt;/p&gt;

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

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

&lt;p&gt;In case anything goes wrong, failures images get generated and come in multiple variants, here in "maskedDiff" mode where images are superposed and only changes are color-highlighted (in pink below) : &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjgp6rzgjknp275m0i4e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjgp6rzgjknp275m0i4e.png" alt="Masked diff golden failure image!" width="364" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or an isolated diff where only the diff is shown :&lt;/p&gt;

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

&lt;p&gt;I just wanted to show you a quick overview of golden tests, I personally love them and use them to quickly generate image variants for various parameters : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Light and Dark themes&lt;/li&gt;
&lt;li&gt;Different orientations (e.g. portrait / landscape)&lt;/li&gt;
&lt;li&gt;Different devices sizes and pixel aspect ratio (e.g. iPhone 5 to iPhone 13)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ℹ️ Just a warning though, testing a lot of variants in each test will quickly increase the number of images being generated and the total execution time. Example : &lt;/p&gt;

&lt;p&gt;&lt;code&gt;4 devices sizes x 2 themes x 2 orientations = 16 tests (for a single test)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You're the master, you decide what you need to do.&lt;/p&gt;

&lt;p&gt;Final advice on golden tests : have a look at &lt;a href="https://pub.dev/packages/alchemist" rel="noopener noreferrer"&gt;Alchemist package&lt;/a&gt; to make your life golden ☀️. An alternative for goldens is the more known &lt;a href="https://pub.dev/packages/golden_toolkit" rel="noopener noreferrer"&gt;golden_toolkit package&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And just to sum up, here's a quick overview of all the tests types in Flutter : &lt;/p&gt;

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

&lt;h3&gt;
  
  
  Code coverage
&lt;/h3&gt;

&lt;p&gt;First of all a disclaimer : I don't want to start a war on whether code coverage should be used as a measure (or the only measure) of code quality. If you rely solely on code coverage to measure code quality, you have probably a long way to go... code coverage in itself does not mean much as a 100% coverage of a piece of code does not the mean that the tests are good and actually test something.&lt;/p&gt;

&lt;p&gt;That said, there are cases when code coverage can be interesting as part of a global code quality measurement, for example you might want to make sure that your code has a decent code coverage and does not get lower and lower as time passes (and code is added).&lt;/p&gt;

&lt;p&gt;Good news, Flutter allows you to just do that with the simple command line argument &lt;code&gt;--coverage&lt;/code&gt;. However, to actually generate a coverage report from the raw result we need to use a combination of &lt;code&gt;junit&lt;/code&gt; and &lt;code&gt;lcov&lt;/code&gt; librairies. I usually use the following script in my projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="ss"&gt;#&lt;/span&gt;&lt;span class="o"&gt;!/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bash&lt;/span&gt;

&lt;span class="ss"&gt;##&lt;/span&gt; &lt;span class="n"&gt;Prepare&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;JUnit&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;
&lt;span class="n"&gt;mkdir&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;flutter&lt;/span&gt; &lt;span class="n"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;global&lt;/span&gt; &lt;span class="n"&gt;activate&lt;/span&gt; &lt;span class="n"&gt;junitreport&lt;/span&gt;

&lt;span class="ss"&gt;##&lt;/span&gt; &lt;span class="n"&gt;Run&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;JUnit&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;
&lt;span class="n"&gt;flutter&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;coverage&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tojunit&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="n"&gt;test_results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;xml&lt;/span&gt;

&lt;span class="ss"&gt;##&lt;/span&gt; &lt;span class="n"&gt;Generate&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;
&lt;span class="n"&gt;lcov&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lcov&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="s"&gt;"lib/main.dart"&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  &lt;span class="s"&gt;"lib/other_file_to_remove_from_coverage.dart"&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lcov_cleaned&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;

&lt;span class="n"&gt;genhtml&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lcov_cleaned&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you execute this script, you should get a report in &lt;code&gt;coverage/&lt;/code&gt; folder that looks like this : &lt;/p&gt;

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

&lt;p&gt;Here you go !&lt;/p&gt;

&lt;h3&gt;
  
  
  CI &amp;amp; CD (Continuous Integration &amp;amp; Continuous Deployment)
&lt;/h3&gt;

&lt;p&gt;I have only one thing to say : &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You must use a CI.&lt;br&gt;
You must use a CI.&lt;br&gt;
You must use a CI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A good CI &amp;amp; CD platform(s) is essential to a successful project for many many reasons, here are a non-exhaustive list : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is a &lt;strong&gt;gain of time&lt;/strong&gt; because you don't have to run each command manually, both for building, testing and deploying your apps&lt;/li&gt;
&lt;li&gt;It is automated, making builds and deploys &lt;strong&gt;repeatable&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It allows for &lt;strong&gt;continuous verification&lt;/strong&gt; of many variables (successful compilation, succeeding tests, etc) and allows for giving constant feedback to the dev team : are there any warnings for potential regressions ?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyway, you must use a CI &amp;amp; CD platform.&lt;/p&gt;

&lt;p&gt;This is not really a Flutter-specific topic but I had to mention it. The only specificity with Flutter builds are related to iOS because iOS apps can only be built on a MacOS runner... yeah 🙌 &lt;/p&gt;

&lt;p&gt;Other than that, you can use pretty much any platform or combination of platforms (and I have used a lot on my various clients projects) as long as you can implement a decent build and a deploy (those steps can be separated though because releases are usually manual) : GitLab CI, Github Actions, Bamboo, Bitrise, Azure Devops, Codemagic...&lt;/p&gt;

&lt;p&gt;By the way, I wrote an article about 2 years ago (might not be up-to-date now...) on how to deploy apps to the stores using &lt;a href="https://dev.to/stack-labs/flutter-a-wonderful-journey-to-ci-with-codemagic-and-fastlane-pj5"&gt;Codemagic in combination with Fastlane&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ideally you want to try and give as much as feedback to your fellow developers on each build : lint checks result, failed tests including golden failures, tests results, code coverage, etc. A very promising Dart package called &lt;a href="https://github.com/danger/dart" rel="noopener noreferrer"&gt;Danger.dart&lt;/a&gt; does just that, and &lt;a href="https://blog.appsynth.net/stop-saying-you-forgot-to-in-code-review-danger-dart-edition-8c61e402cd4e" rel="noopener noreferrer"&gt;here is an article that explains the process a little bit more&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code quality measurement
&lt;/h3&gt;

&lt;p&gt;If you are using the tools presented in this article you should by now have pretty good measures of your app code quality. Linters, tests, code coverage, etc.&lt;/p&gt;

&lt;p&gt;However, some people might still want some kind of dashboard to gather all code quality related measures into one place.&lt;/p&gt;

&lt;p&gt;Ring any bell ? Don't be shy, I know that you are thinking of &lt;a href="https://www.sonarqube.org" rel="noopener noreferrer"&gt;Sonarqube&lt;/a&gt; ! &lt;/p&gt;

&lt;p&gt;And luckily for us, there is now a Flutter / Dart package available to plug into Sonarqube for our loved apps analysis !&lt;/p&gt;

&lt;p&gt;This library called &lt;a href="https://github.com/insideapp-oss/sonar-flutter" rel="noopener noreferrer"&gt;sonar-flutter&lt;/a&gt; generates the well-known Sonarqube dashboard after taking into account all Dart &amp;amp; Flutter related measures :&lt;/p&gt;

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

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

&lt;p&gt;In this article we saw that code quality is very important to make a Flutter app more safe, reliable and maintainable. We also had a look at a few tools that can make our life easier when trying to improve code quality in our apps.&lt;/p&gt;

&lt;p&gt;Please note that there are many more techniques that I did not mention earlier such as pair-programming, code reviews, documentation or TDD. Because they are general techniques, I chose to focus on specific Dart or Flutter tools here to try and give you a good overview of what you can do. Obviously you can use any known technique in addition to the specific tools if you feel the need to : &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your team is the only master of your app quality !&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>codequality</category>
      <category>dart</category>
    </item>
    <item>
      <title>CLI applications made easy with Dart &amp; dcli</title>
      <dc:creator>Olivier Revial</dc:creator>
      <pubDate>Tue, 02 Nov 2021 09:15:12 +0000</pubDate>
      <link>https://dev.to/stack-labs/cli-applications-made-easy-with-dart-dcli-8af</link>
      <guid>https://dev.to/stack-labs/cli-applications-made-easy-with-dart-dcli-8af</guid>
      <description>&lt;p&gt;I've been developing Flutter applications for a while now and I love Flutter and Dart, in particular since Dart 2.12 / Flutter 2.0 when null safety was officially released !&lt;/p&gt;

&lt;p&gt;Sometimes however, you need a good ol' bash script to run some chore stuff or for your CI... and I have to admit, I suck at writing clean bash scripts.&lt;/p&gt;

&lt;p&gt;💭 If only I could write my scripts using the same language I use everyday, the very language I'm the most familiar with at the moment ? 🤔&lt;/p&gt;

&lt;p&gt;Today I will show you how we can write command-line applications using only Dart with a few librairies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we will do
&lt;/h2&gt;

&lt;p&gt;The goal of our application will be to simply release a new version of our app by updating the version number in our project's &lt;code&gt;pubspec.yaml&lt;/code&gt;. This is something that could later be executed  by a CI to automate this, we will do it by hand here for the sake of the example. Also, you can find the &lt;a href="https://github.com/orevial/dart-release-cli" rel="noopener noreferrer"&gt;code in this repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main steps we will follow are :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write our CLI&lt;/li&gt;
&lt;li&gt;Run our CLI within Dart environment&lt;/li&gt;
&lt;li&gt;Create a standalone CLI that can be run as a simple script&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's start by generating a simple Dart application :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;dart&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;dart&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt;
&lt;span class="n"&gt;dart&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, we now have a sample project that we can work from :&lt;/p&gt;

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

&lt;p&gt;It's now time to add actual CLI-related code !&lt;/p&gt;

&lt;p&gt;To do that, we could use Dart's out-of-the-box support for command line code (e.g. reading input or printing output, I/O related operations and so on). The process to do a bunch of CLI-related operations is very well documented by Dart team in this great Dart tutorial "[Write command-line apps]"(&lt;a href="https://dart.dev/tutorials/server/cmdline" rel="noopener noreferrer"&gt;https://dart.dev/tutorials/server/cmdline&lt;/a&gt;). However I find that default libraries are not so simple to use and I don't like to have a lot of boilerplate code to do simple CLI things such as reading an input or writing to a file.&lt;/p&gt;

&lt;p&gt;Fortunately I'm not the only one and so people have created a dedicated wrapper around Dart native CLI code : a package named &lt;a href="https://pub.dev/packages/dcli" rel="noopener noreferrer"&gt;&lt;code&gt;dcli&lt;/code&gt;&lt;/a&gt;. And, bonus, the &lt;a href="https://dcli.noojee.dev/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; is pretty cool and rather comprehensive !&lt;/p&gt;

&lt;p&gt;Enough talking, let's add this package as a dev dependency. We will also add &lt;a href="https://pub.dev/packages/yaml" rel="noopener noreferrer"&gt;&lt;code&gt;yaml&lt;/code&gt; package&lt;/a&gt;, we will need it to easily read our pubspec.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#pubspec.yaml:&lt;/span&gt;

&lt;span class="na"&gt;dev_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dcli&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^1.9.6&lt;/span&gt;
  &lt;span class="na"&gt;yaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^3.1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, don't forget to run &lt;code&gt;pub get&lt;/code&gt; to update dependencies.&lt;/p&gt;

&lt;p&gt;We can now write our CLI to &lt;code&gt;bin/release.dart&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="ss"&gt;#&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="n"&gt;dcli&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:io'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:dcli/dcli.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:yaml/yaml.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Display output to console&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'''# ------------------------------
# 🚀 Release new version
# ------------------------------'''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Ask for user input&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;newVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;'Enter new version number:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;validator:&lt;/span&gt; &lt;span class="n"&gt;Ask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;regExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="sx"&gt;r'^\d+\.\d+\.\d+$'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;error:&lt;/span&gt; &lt;span class="s"&gt;'You must pass a valid semver version in the form X.Y.Z'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Read a file&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;pubspec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'./pubspec.yaml'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readAsStringSync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Parse yaml content to retrieve current version number&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;currentVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loadYaml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pubspec&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s"&gt;'version'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'# Updating from version &lt;/span&gt;&lt;span class="si"&gt;$currentVersion&lt;/span&gt;&lt;span class="s"&gt; to version &lt;/span&gt;&lt;span class="si"&gt;$newVersion&lt;/span&gt;&lt;span class="s"&gt;...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Write to a file&lt;/span&gt;
    &lt;span class="s"&gt;'pubspec.yaml'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pubspec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replaceFirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'version: &lt;/span&gt;&lt;span class="si"&gt;$currentVersion&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;'version: &lt;/span&gt;&lt;span class="si"&gt;$newVersion&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'# ✅ Pubspec updated to version &lt;/span&gt;&lt;span class="si"&gt;$newVersion&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Print or treat error as needed&lt;/span&gt;
    &lt;span class="n"&gt;printerr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'# ❌ Failed to update pubspec to version &lt;/span&gt;&lt;span class="si"&gt;$newVersion&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;$e&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it !&lt;/p&gt;

&lt;p&gt;I won't go through all the code because it's pretty basic stuff, but a few things can be noted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The very first line is a shebang that allows us to run our dart file as if it were a simple shell script. To do that you first need to &lt;a href="https://dcli.noojee.dev/getting-started#option-2-install-dart-dcli" rel="noopener noreferrer"&gt;globally install DCli tools&lt;/a&gt;. Note that this is &lt;a href="https://dcli.noojee.dev/dcli-tools-1/use-a-shebang" rel="noopener noreferrer"&gt;dcli recommended solution&lt;/a&gt; but it works just as well with a simple dart shebang (&lt;code&gt;#! /usr/bin/env dart&lt;/code&gt;) 😉&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;print&lt;/code&gt; (Dart's one) and &lt;code&gt;printerr&lt;/code&gt; (DCli's one) will both output to the console, respectively to stdout and stderr of course. 📝&lt;/li&gt;
&lt;li&gt;DCli offers cool APIs such as the &lt;a href="https://dcli.noojee.dev/what-does-dcli-do#user-input" rel="noopener noreferrer"&gt;&lt;code&gt;ask&lt;/code&gt; operator&lt;/a&gt;, a one-line solution to ask for user input and optionally &lt;a href="https://dcli.noojee.dev/dcli-api/user-input/ask-validators" rel="noopener noreferrer"&gt;validate user input&lt;/a&gt; at the same time. 
Another example is the &lt;a href="https://dcli.noojee.dev/what-does-dcli-do#read-write-files" rel="noopener noreferrer"&gt;&lt;code&gt;write&lt;/code&gt;&lt;/a&gt; command we use, a String extension that wraps Dart file handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alright, time to run our CLI ! We have quite a few options here.&lt;/p&gt;

&lt;p&gt;The first is to run our file using Dart VM :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart bin/release.dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second solution, as explained above, is to use &lt;a href="https://dcli.noojee.dev/getting-started#option-2-install-dart-dcli" rel="noopener noreferrer"&gt;globally installed DCli tools&lt;/a&gt; or &lt;code&gt;dart&lt;/code&gt; shebang to run our Dart file as if it were a simple script, so we just have have to execute it :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Make sure your file is executable first&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x bin/release.dart

&lt;span class="c"&gt;# Run as a simple script&lt;/span&gt;
./bin/release.dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully you had expected output by now ! This is great and allows for quick testing, however you may have noticed that it takes quite some time before the first line is actually displayed. Well, it's normal because the code needs to be compiled before it can be run. Fortunately, Dart offers the ability to generate a platform executable easily using &lt;a href="https://dart.dev/tools/dart-compile" rel="noopener noreferrer"&gt;&lt;code&gt;dart compile&lt;/code&gt;&lt;/a&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart compile exe bin/release.dart &lt;span class="nt"&gt;-o&lt;/span&gt; release

🕙 Info: Compiling with sound null safety
🕙 Generated: dart-release-cli/release

&lt;span class="c"&gt;# Time to run !&lt;/span&gt;
./release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have an executable that run instantly. In a real world it would probably be the CI job to compile and produce the executable. Also, note that the only drawback is that the executable is much larger than a simple Dart file (about 5MB for our example CLI).&lt;/p&gt;

&lt;p&gt;Alright, that's about it ! Here's a demo of the running CLI :&lt;/p&gt;

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

&lt;p&gt;I hope you enjoyed this article where I tried to show you a simple way to generate powerful CLI applications using Dart and a few libraries. And if you want to test the CLI, head over to the &lt;a href="https://github.com/orevial/dart-release-cli" rel="noopener noreferrer"&gt;Github repository&lt;/a&gt; 👋&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/dcli" rel="noopener noreferrer"&gt;&lt;code&gt;dcli&lt;/code&gt; package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/yaml" rel="noopener noreferrer"&gt;&lt;code&gt;yaml&lt;/code&gt; package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dcli.noojee.dev/" rel="noopener noreferrer"&gt;DCli documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dart.dev/tools/dart-compile" rel="noopener noreferrer"&gt;Dart compile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dart.dev/tutorials/server/cmdline#running-an-app-with-the-standalone-dart-vm" rel="noopener noreferrer"&gt;Dart command-line documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
    </item>
    <item>
      <title>Flutter: flavors launcher icons made simple</title>
      <dc:creator>Olivier Revial</dc:creator>
      <pubDate>Mon, 11 Jan 2021 08:43:14 +0000</pubDate>
      <link>https://dev.to/stack-labs/flutter-flavors-launcher-icons-made-simple-336j</link>
      <guid>https://dev.to/stack-labs/flutter-flavors-launcher-icons-made-simple-336j</guid>
      <description>&lt;p&gt;Anyone that has already dealt with &lt;a href="https://flutter.dev/docs/deployment/flavors" rel="noopener noreferrer"&gt;flavors in Flutter&lt;/a&gt; knows that they are not such an easy thing to implement. &lt;/p&gt;

&lt;p&gt;You usually end up reading a lot of articles, going back and forth between your favorite IDE and Xcode, looking at configuration in your &lt;code&gt;build.gradle&lt;/code&gt; and then crying over Xcode build configs...&lt;/p&gt;

&lt;p&gt;But one thing is pretty easy to do when using flavors : generating appropriate launcher icons for both platforms, for all resolutions and obviously each flavor.&lt;/p&gt;

&lt;p&gt;Supposing we have development, integration and production flavor, what we want to do in the end is have all these icons for Android:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkawu4aztn74fqsjybols.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkawu4aztn74fqsjybols.jpg" alt="flutter-flavor-android" width="428" height="868"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the same goes for iOS:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffyy57un632u8qrnnkx66.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffyy57un632u8qrnnkx66.jpg" alt="flutter-flavor-ios" width="636" height="1168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't know about you, but I really don't want to generate all these icons by hand... so we will instead use the great &lt;a href="https://pub.dev/packages/flutter_launcher_icons" rel="noopener noreferrer"&gt;Flutter plugin flutter_launcher_icons&lt;/a&gt; !&lt;/p&gt;

&lt;p&gt;As it is just a generator that we will use once, we don't need it at runtime so we can add it to our &lt;code&gt;pubspec.yaml&lt;/code&gt; &lt;code&gt;dev_dependencies&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dev_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="s"&gt;flutter_launcher_icons&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.8.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we will need to configure the assets location for each flavor:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;flutter_launcher_icons-development.yaml&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flutter_icons&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;android&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;ios&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;image_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assets/launcher_icon/launcher-icon-dev.png"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;flutter_launcher_icons-integration.yaml&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flutter_icons&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;android&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;ios&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;image_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assets/launcher_icon/launcher-icon-int.png"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;flutter_launcher_icons-production.yaml&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;flutter_icons&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;android&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;ios&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;image_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assets/launcher_icon/launcher-icon-prod.png"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have our plugin configured for each flavor, it's time to add our actual icons in our assets:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4ydx9yhlbv1w7huj59aj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4ydx9yhlbv1w7huj59aj.png" alt="Capture d’écran 2021-01-08 à 16.18.17" width="279" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you probably understood already, the next step will generate all the icons shapes and resolutions as needed by each platform. This process will take our source icon for each flavor and will downscale it to create all resolutions. This is important because it means you should provide the highest resolution of 1024x1024 for source icons. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: at this point our assets are not related to anything, i.e. not used by either Android or iOS, so we need to translate them in their respective format:&lt;/p&gt;

&lt;p&gt;Let's do just that by invoking a run of our plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter pub get
flutter pub run flutter_launcher_icons:main &lt;span class="nt"&gt;-f&lt;/span&gt; flutter_launcher_icons&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few seconds, you should get a result similar to this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;════════════════════════════════════════════
   FLUTTER LAUNCHER ICONS &lt;span class="o"&gt;(&lt;/span&gt;v0.8.0&lt;span class="o"&gt;)&lt;/span&gt;
════════════════════════════════════════════

Flavor: development
• Creating default icons Android
• Overwriting the default Android launcher icon with a new icon
• Building iOS launcher icon &lt;span class="k"&gt;for &lt;/span&gt;development

Flavor: integration
• Creating default icons Android
• Overwriting the default Android launcher icon with a new icon
• Building iOS launcher icon &lt;span class="k"&gt;for &lt;/span&gt;integration

Flavor: production
• Creating default icons Android
• Overwriting the default Android launcher icon with a new icon
• Building iOS launcher icon &lt;span class="k"&gt;for &lt;/span&gt;production

✓ Successfully generated launcher icons &lt;span class="k"&gt;for &lt;/span&gt;flavors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voilà, you now have brand new icons for all platforms, flavors and resolutions conbinations. &lt;/p&gt;

&lt;p&gt;Pretty neat, right ?&lt;/p&gt;

&lt;p&gt;As a side note, the plugin is also able to generate &lt;a href="https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive" rel="noopener noreferrer"&gt;adaptative Android icons&lt;/a&gt; by providing &lt;code&gt;adaptive_icon_background&lt;/code&gt; and &lt;code&gt;adaptive_icon_foreground&lt;/code&gt; for Android, which I highly recommend !&lt;/p&gt;




&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/flutter_launcher_icons" rel="noopener noreferrer"&gt;flutter_launcher_icons plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive" rel="noopener noreferrer"&gt;Android adaptative icons&lt;/a&gt; and &lt;a href="https://developer.android.com/studio/write/image-asset-studio" rel="noopener noreferrer"&gt;how to create them&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/" rel="noopener noreferrer"&gt;iOS app icons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cogitas.net/creating-flavors-of-a-flutter-app/" rel="noopener noreferrer"&gt;Creating flavors of a Flutter app&lt;/a&gt;
* &lt;a href="https://medium.com/@salvatoregiordanoo/flavoring-flutter-392aaa875f36" rel="noopener noreferrer"&gt;Flavoring Flutter&lt;/a&gt;
* &lt;a href="https://medium.com/flutter-community/flutter-ready-to-go-e59873f9d7de" rel="noopener noreferrer"&gt;Flutter Ready to Go&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://itnext.io/flutter-1-17-no-more-flavors-no-more-ios-schemas-command-argument-that-solves-everything-8b145ed4285d" rel="noopener noreferrer"&gt;No more Flavors, no more iOS Schemas. Command argument that changes everything&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Flutter, a wonderful journey from 0 to app stores - CI featuring Codemagic and Fastlane</title>
      <dc:creator>Olivier Revial</dc:creator>
      <pubDate>Tue, 03 Nov 2020 17:54:33 +0000</pubDate>
      <link>https://dev.to/stack-labs/flutter-a-wonderful-journey-to-ci-with-codemagic-and-fastlane-pj5</link>
      <guid>https://dev.to/stack-labs/flutter-a-wonderful-journey-to-ci-with-codemagic-and-fastlane-pj5</guid>
      <description>&lt;p&gt;So you just started using &lt;a href="https://flutter.dev/" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt; and already love its &lt;a href="https://flutter.dev/docs/development/ui/widgets" rel="noopener noreferrer"&gt;wonderful and numerous widgets&lt;/a&gt;, but as your project is being serious you're wondering how to sign your Android &amp;amp; iOS apps and how to deploy them to both stores ? Oh, and as a &lt;del&gt;lazy&lt;/del&gt; good developer, you're obvisouly wondering how to automate your builds and deployments to reduce manual actions (and errors) and free up some of your precious time to code your beautiful app ?&lt;/p&gt;

&lt;p&gt;Well, me too. At first I thought it would be very simple and I'd just have to configure the awesome &lt;a href="https://codemagic.io/" rel="noopener noreferrer"&gt;Codemagic&lt;/a&gt;, use its 500 free minutes per month... and relax 🛁 &lt;br&gt;&lt;br&gt;
After all, it's supposed to be magic 🧙 !&lt;/p&gt;

&lt;p&gt;In this article I will explain step-by-step how to easily automate app building and beta deployment to Android &amp;amp; iOS stores.&lt;/p&gt;

&lt;p&gt;❗️📝&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I am not claiming to have the best approach of all, but I needed a reminder of all the steps needed and I thought it could benefit others... tell me if you have better ways to do it or if I can improve my solution !&lt;/li&gt;
&lt;li&gt;In this article we will only explore how to deploy to testing tracks (beta track for iOS and internal tests for Android), but the process should be pretty much the same for other tracks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The various chapters we will detail are the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;👥 Prerequisites: all the accounts and things you need before setting up the CI&lt;/li&gt;
&lt;li&gt;🧙‍♂️ Codemagic setup: setup of the CI workflow(s)&lt;/li&gt;
&lt;li&gt;🤖 Android setup
&lt;/li&gt;
&lt;li&gt;🍏📱 iOS setup
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;📁 You can find my &lt;a href="https://github.com/orevial/flutter-codemagic-demo" rel="noopener noreferrer"&gt;demo repository on Github&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  Prerequisites &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Hopefully you are reading this article before you actually &lt;strong&gt;need&lt;/strong&gt; the CI, because asking all people in the company to create the various accounts may take some time.&lt;/p&gt;
&lt;h2&gt;
  
  
  👥 Accounts &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In order to connect to different platforms through Codemagic you will need a few accounts and keys to be setup before you start implementing actual CI pipeline.&lt;/p&gt;

&lt;p&gt;In particular you need the following accounts and keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/programs/enroll/" rel="noopener noreferrer"&gt;Apple enrolled account&lt;/a&gt;. You basically need a paid Apple Developer account, either enrolling as an individual or as an organization.

&lt;ul&gt;
&lt;li&gt;Example: &lt;em&gt;&lt;a href="mailto:support@your-company.com"&gt;support@your-company.com&lt;/a&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://appstoreconnect.apple.com/apps/1515747150/testflight/ios" rel="noopener noreferrer"&gt;Apple Developer account&lt;/a&gt;: Used to handle &lt;a href="https://developer.apple.com/account/resources/certificates/list" rel="noopener noreferrer"&gt;iOS profiles &amp;amp; certificates&lt;/a&gt; and to &lt;a href="https://appstoreconnect.apple.com/apps/1537567267/testflight" rel="noopener noreferrer"&gt;deploy iOS builds to TestFlight&lt;/a&gt;.

&lt;ul&gt;
&lt;li&gt;This account must have App Manager role (not juste Developer role).&lt;/li&gt;
&lt;li&gt;This account will be the same as the enrolled account if you're an individual, otherwise you should probably keep things separate and create this account as a "technical account".&lt;/li&gt;
&lt;li&gt;Example: &lt;em&gt;&lt;a href="mailto:apple-technical@your-company.com"&gt;apple-technical@your-company.com&lt;/a&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.google.com/googleplay/android-developer/answer/6112435?hl=en" rel="noopener noreferrer"&gt;Google Play Developer enrolled account&lt;/a&gt;. Again, you need a paid Google Play Developer account if you want to publish your apps.

&lt;ul&gt;
&lt;li&gt;Example: &lt;em&gt;&lt;a href="mailto:support@your-company.com"&gt;support@your-company.com&lt;/a&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.fastlane.tools/actions/supply/#setup" rel="noopener noreferrer"&gt;Google Play Console service account key&lt;/a&gt;. A JSON key used to deploy &lt;a href="https://play.google.com/console" rel="noopener noreferrer"&gt;Android builds to Google Play beta store&lt;/a&gt;.

&lt;ul&gt;
&lt;li&gt;This account must have Release Manager role.&lt;/li&gt;
&lt;li&gt;This key will later be used in Google Play service account key chapter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://play.google.com/console/developers/" rel="noopener noreferrer"&gt;Google Play Console account&lt;/a&gt;: your personal account

&lt;ul&gt;
&lt;li&gt;This account must have at least Release Manager role.&lt;/li&gt;
&lt;li&gt;Strictly speaking this account won't be used by Codemagic (it will only use service account JSON key mentionned above) but you will need it to manually upload your first build or to to fill app information in Google Play Console, promote the build to production and so on.&lt;/li&gt;
&lt;li&gt;Example: Google developer account like &lt;em&gt;&lt;a href="mailto:your.name@your-company.com"&gt;your.name@your-company.com&lt;/a&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gitlab/Github/Bitbucket&lt;/strong&gt; account. Used by Codemagic to checkout Flutter app's repository.

&lt;ul&gt;
&lt;li&gt;Make sure this account has access to your repository by checking repository Members (e.g. in &lt;a href="https://docs.gitlab.com/ee/user/project/members/" rel="noopener noreferrer"&gt;Gitlab members&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;You need to generate SSH key pair and upload the public key to your profile (e.g. &lt;a href="https://gitlab.com/-/profile/keys" rel="noopener noreferrer"&gt;Gitlab SSH keys&lt;/a&gt;). You can name the public key anything, I like to put something like &lt;code&gt;Codemagic app XXX&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Keep the private key, you will need to encrypt it and pass it to Codemagic later on !&lt;/li&gt;
&lt;li&gt;Example: &lt;em&gt;&lt;a href="mailto:codemagic@your-company.com"&gt;codemagic@your-company.com&lt;/a&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codemagic.io/" rel="noopener noreferrer"&gt;Codemagic account&lt;/a&gt;. Used to configure Codemagic pipeline itself, Codemagic account is required to run any build as each account has a limited number of build minutes available (500 by default).

&lt;ul&gt;
&lt;li&gt;For simplicity reasons I use the same Gitlab account mentioned above to connect to Codemagic (e.g. &lt;em&gt;&lt;a href="mailto:codemagic@your-company.com"&gt;codemagic@your-company.com&lt;/a&gt;&lt;/em&gt; &lt;strong&gt;Gitlab&lt;/strong&gt; account).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Configuration &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Once you have your personal accounts for both Apple Connect and Google Play Console, you should take some time to create the application page for both stores as you won't be able to deploy Android or iOS test apps before you filled a minimum information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://play.google.com/console/" rel="noopener noreferrer"&gt;Google Play Console&lt;/a&gt; : create a new application in the console.

&lt;ul&gt;
&lt;li&gt;Make sure you enter a correct package name, it's definitive !&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://play.google.com/console/" rel="noopener noreferrer"&gt;Google Play Console&lt;/a&gt; : fill-in basic information. Start at app's dashboard and follow the guide to fill all required steps (there are a lot of steps but it's pretty simple)

&lt;ul&gt;
&lt;li&gt;You will need to upload at least an app icon (make sure you respect appropriate format), a Play Store commercial banner and 2 screenshots of the app before you can upload your first APK (yes, even for internal tests).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/account/resources/identifiers/list" rel="noopener noreferrer"&gt;Apple Developer identifiers&lt;/a&gt; : create a new identifier of type &lt;code&gt;App IDs&lt;/code&gt; and follow the steps.

&lt;ul&gt;
&lt;li&gt;Note generated &lt;code&gt;bundleId&lt;/code&gt; and &lt;code&gt;App team id&lt;/code&gt;, you will need them later.&lt;/li&gt;
&lt;li&gt;Make sure you enter a correct bundle id, it's definitive !&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://appstoreconnect.apple.com/" rel="noopener noreferrer"&gt;Appstore Connect&lt;/a&gt; : create a new application in the console. You will need to link your new app to a &lt;code&gt;bundleId&lt;/code&gt;... the one you created in previous step 🤘&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://appstoreconnect.apple.com/" rel="noopener noreferrer"&gt;Appstore Connect&lt;/a&gt; : fill-in basic information.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  🧙‍♂️ Codemagic setup &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Codemagic use multiple files to do its "magic":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/codemagic.yaml" rel="noopener noreferrer"&gt;codemagic.yaml main file&lt;/a&gt;: defines various worflow for iOS, Android and both apps deployments.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/codemagic/post-clone.sh" rel="noopener noreferrer"&gt;Codemagic post-clone setup file&lt;/a&gt;: various installation steps for both Android and iOS apps&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/android/Gemfile" rel="noopener noreferrer"&gt;Gemfile for Android&lt;/a&gt; and &lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/ios/Gemfile" rel="noopener noreferrer"&gt;for iOS&lt;/a&gt;. Used to automate gem installation on Codemagic using &lt;code&gt;bundle install&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/android/fastlane/" rel="noopener noreferrer"&gt;Fastlane for Android&lt;/a&gt; and &lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/ios/fastlane/" rel="noopener noreferrer"&gt;Fastlane for iOS&lt;/a&gt;: we use &lt;a href="https://docs.fastlane.tools/" rel="noopener noreferrer"&gt;Fastlane&lt;/a&gt; for all build and deploy steps to help us handle certificate management, app signing and similar boring stuff.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start right away with Codemagic main file, &lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/codemagic.yaml" rel="noopener noreferrer"&gt;codemagic.yaml&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;internal-deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Internal deployment&lt;/span&gt;
    &lt;span class="na"&gt;max_build_duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;BUILD_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.2.3&lt;/span&gt;
        &lt;span class="na"&gt;GOOGLE_PLAY_STORE_JSON_BASE64&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Encrypted(var)&lt;/span&gt;
        &lt;span class="na"&gt;ANDROID_KEYSTORE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Encrypted(var)&lt;/span&gt;
        &lt;span class="na"&gt;ANDROID_KEYSTORE_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Encrypted(var)&lt;/span&gt;
        &lt;span class="na"&gt;ANDROID_KEY_ALIAS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Encrypted(var)&lt;/span&gt;
        &lt;span class="na"&gt;ANDROID_KEY_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Encrypted(var)&lt;/span&gt;
        &lt;span class="na"&gt;IOS_APP_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;com.company.somename&lt;/span&gt;
        &lt;span class="na"&gt;APPLE_DEVELOPER_TEAM_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;123ABCD456&lt;/span&gt;
        &lt;span class="na"&gt;FASTLANE_EMAIL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apple-technical@your-company.com&lt;/span&gt;
        &lt;span class="na"&gt;FASTLANE_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Encrypted(var)&lt;/span&gt;
        &lt;span class="na"&gt;MATCH_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Encrypted(var)&lt;/span&gt;
        &lt;span class="na"&gt;SSH_KEY_FOR_FASTLANE_MATCH_BASE64&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Encrypted(var)&lt;/span&gt;
        &lt;span class="na"&gt;FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Encrypted(var)&lt;/span&gt;
      &lt;span class="na"&gt;flutter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
      &lt;span class="na"&gt;xcode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;
      &lt;span class="na"&gt;cocoapods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
    &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;cache_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$HOME/Library/Caches/CocoaPods&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$HOME/.gradle/caches&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$FLUTTER_ROOT/.pub-cache&lt;/span&gt;
    &lt;span class="na"&gt;triggering&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;tag&lt;/span&gt;
    &lt;span class="na"&gt;scripts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Post clone setup for Android &amp;amp; iOS&lt;/span&gt;
        &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;#!/usr/bin/env sh&lt;/span&gt;
            &lt;span class="s"&gt;/bin/sh $FCI_BUILD_DIR/codemagic/post-clone.sh all&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build &amp;amp; Deploy Android app&lt;/span&gt;
        &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cd $FCI_BUILD_DIR/android&lt;/span&gt;

          &lt;span class="s"&gt;bundle exec fastlane supply init --track internal --package_name com.company.somename&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build &amp;amp; Deploy iOS app&lt;/span&gt;
        &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cd $FCI_BUILD_DIR/ios&lt;/span&gt;

          &lt;span class="s"&gt;bundle exec fastlane ios beta&lt;/span&gt;
    &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build/**/outputs/**/*.apk&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build/**/outputs/**/*.aab&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build/ios/ipa/*.ipa&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/tmp/xcodebuild_logs/*.log&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;flutter_drive.log&lt;/span&gt;
    &lt;span class="na"&gt;publishing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;slack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#flutter-codemagic-demo'&lt;/span&gt;
        &lt;span class="na"&gt;notify_on_build_start&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're familiar with &lt;a href="https://docs.gitlab.com/ee/ci/yaml/README.html" rel="noopener noreferrer"&gt;Gitlab CI&lt;/a&gt; or YAML in general, you shouldn't be too shocked reading this file. Let's describe the behaviour of your one and only workflow, &lt;em&gt;internal-deployment&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔫 &lt;code&gt;max_build_duration&lt;/code&gt;: a timeout duration, because sometimes Codemagic/Android/iOS builds get stuck.&lt;/li&gt;
&lt;li&gt;✓ &lt;code&gt;environment&lt;/code&gt;: all environment variables that will be used by our scripts. More on this in Android and iOS setup. Also defines the versions of Flutter, Xcode and Cocoapods that Codemagic should use. It is good practice to use latest stable versions.&lt;/li&gt;
&lt;li&gt;🏎 &lt;code&gt;cache&lt;/code&gt;: files that should be cached and reused across multiple builds to save some precious build minutes.&lt;/li&gt;
&lt;li&gt;🔘 &lt;code&gt;triggering&lt;/code&gt;: how the build should be triggered. I decided that mine should be triggered every time I release a new tag in my Gitlab project. Note that I had to add a Gitlab webhook in order to automatically trigger Codemagic on tag creation.&lt;/li&gt;
&lt;li&gt;▶️ &lt;code&gt;scripts&lt;/code&gt;: the core of our workflow: what we should do when this Codemagic workflow is triggered. As you can see here we only delegate the work to Fastlane for both Android and iOS, as we will see in a minute.&lt;/li&gt;
&lt;li&gt;📦 &lt;code&gt;artifacts&lt;/code&gt;: the artifacts we want to publish on Codemagic. Not solely needed as artifacts will be published to iOS and Android consoles but I like to keep them here in case the build fails and I need to debug.&lt;/li&gt;
&lt;li&gt;📩 &lt;code&gt;publishing&lt;/code&gt;: I like to be notified when my build fails or succeeds so I configured a Slack notification here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first script called by our worflow is a "post-clone" script, let's see this &lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/codemagic/post-clone.sh" rel="noopener noreferrer"&gt;codemagic/post-clone.sh&lt;/a&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env sh&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="c"&gt;# exit on first failed commandset&lt;/span&gt;

installGems&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Installing gems..."&lt;/span&gt;

  bundle &lt;span class="nb"&gt;install
  &lt;/span&gt;bundle update fastlane
  bundle update signet
&lt;span class="o"&gt;}&lt;/span&gt;

androidSteps&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"========================================"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"|       Android post-clone steps       |"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"========================================"&lt;/span&gt;

  &lt;span class="c"&gt;# set up key.properties&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$ANDROID_KEYSTORE&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/keystore.keystore
  &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FCI_BUILD_DIR&lt;/span&gt;&lt;span class="s2"&gt;/android/key.properties"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
storePassword=&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_KEYSTORE_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;
keyAlias=&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_KEY_ALIAS&lt;/span&gt;&lt;span class="sh"&gt;
keyPassword=&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_KEY_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;
storeFile=/tmp/keystore.keystore
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;  &lt;span class="c"&gt;# set up local properties&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"flutter.sdk=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/programs/flutter"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FCI_BUILD_DIR&lt;/span&gt;&lt;span class="s2"&gt;/android/local.properties"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--- Generate Google Service key for Android"&lt;/span&gt;
  &lt;span class="nv"&gt;GOOGLE_PLAY_STORE_JSON_KEY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FCI_BUILD_DIR&lt;/span&gt;&lt;span class="s2"&gt;/android/app/google-play-store.json"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$GOOGLE_PLAY_STORE_JSON_BASE64&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$GOOGLE_PLAY_STORE_JSON_KEY_PATH&lt;/span&gt;

  &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$FCI_BUILD_DIR&lt;/span&gt;/android
  installGems
&lt;span class="o"&gt;}&lt;/span&gt;

iosSteps&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"========================================"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"|         iOS post-clone steps         |"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"========================================"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--- Generate SSH key for Gitlab access from Fastlane Match"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SSH_KEY_FOR_FASTLANE_MATCH_BASE64&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/bkey

  &lt;span class="c"&gt;# adding custom ssh key to access private repository&lt;/span&gt;
  &lt;span class="nb"&gt;chmod &lt;/span&gt;600 /tmp/bkey
  &lt;span class="nb"&gt;cp&lt;/span&gt; /tmp/bkey ~/.ssh/bkey
  ssh-add ~/.ssh/bkey

  &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$FCI_BUILD_DIR&lt;/span&gt;/ios
  installGems
&lt;span class="o"&gt;}&lt;/span&gt;

androidSteps
iosSteps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script is pretty boring but what it does is install a bunch of gems and setup some files that iOS and Android will need for building, signing and publishing the apps.&lt;/p&gt;

&lt;p&gt;As you can see there is a bundle install that will use &lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/android/Gemfile" rel="noopener noreferrer"&gt;Android Gemfile&lt;/a&gt; and &lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/ios/Gemfile" rel="noopener noreferrer"&gt;iOS Gemfile&lt;/a&gt; to automatically install required gems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android Gemfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"https://rubygems.org"&lt;/span&gt;

gem &lt;span class="s2"&gt;"fastlane"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;iOS Gemfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"https://rubygems.org"&lt;/span&gt;

gem &lt;span class="s2"&gt;"fastlane"&lt;/span&gt;
gem &lt;span class="s2"&gt;"cocoapods"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's now see Android build script in &lt;a href="https://github.com/orevial/flutter-codemagic-demo/blob/main/android/fastlane/Fastfile" rel="noopener noreferrer"&gt;android/fastlane/Fastfile&lt;/a&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default_platform(:android)

platform :android do
  desc "Deploy a new internal build to Google Play"
  lane :internal do
    Dir.chdir "../.." do
      sh("flutter", "packages", "get")
      sh("flutter", "clean")
      sh("flutter", "build", "appbundle", "--build-number=#{ENV["PROJECT_BUILD_NUMBER"]}", "--build-name=#{ENV["BUILD_NAME"]}")
    end
    upload_to_play_store(
        track: 'internal',
        aab: '../build/app/outputs/bundle/release/app-release.aab'
    )
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script basically uses &lt;a href="https://docs.fastlane.tools/" rel="noopener noreferrer"&gt;Fastlane&lt;/a&gt; to build and deploy the app to Google Play Console. First it defines a "lane", which is just Fastlane way of defining a "worflow". Then we use shell to initialize and build our flutter app, by passing it build name and number from environment variables.&lt;/p&gt;

&lt;p&gt;Once the signed application is ready, we use Fastlane once again to publish this app to the &lt;em&gt;internal&lt;/em&gt; Android track through &lt;a href="https://docs.fastlane.tools/actions/upload_to_play_store/" rel="noopener noreferrer"&gt;upload_to_play_store&lt;/a&gt; task.&lt;/p&gt;

&lt;p&gt;Pretty simple right ? 🎉&lt;/p&gt;

&lt;p&gt;Let's now see the equivalent for iOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default_platform(:ios)

platform :ios do
  before_all do
      # Create a local keychain that will later store iOS profiles and certificates
      if is_ci?
          puts "This is CI run. Setting custom keychain."
          create_keychain(
              name: 'Temp.codemagic_keychain',
              password: 'Temp.codemagic_keychain_password',
              default_keychain: true,
              unlock: true,
              timeout: 3600,
          )
      end
  end

  desc "Push a new beta build to TestFlight"
  lane :beta do
    # Synchronize profiles &amp;amp; certificates from Git repo using Match
    match(
        type: "appstore",
        readonly: is_ci,
        keychain_name: 'Temp.codemagic_keychain',
        keychain_password: 'Temp.codemagic_keychain_password'
    )
    # Disable automatic code signing as we will use custom signing method later on
    update_code_signing_settings(
      use_automatic_signing: false
    )
    # Update Xcode provisioning profile with the one we got from Git repo using Match
    update_project_provisioning(
      # https://github.com/fastlane/fastlane/issues/15926
      profile: ENV["sigh_#{ENV["IOS_APP_ID"]}_appstore_profile-path"],
      build_configuration: "Release",
      code_signing_identity: "iPhone Distribution",
      xcodeproj: "Runner.xcodeproj",
    )
    # Replace version number with Codemagic build number
    set_info_plist_value(
      path: "Runner/Info.plist",
      key: "CFBundleVersion",
      value: ENV["PROJECT_BUILD_NUMBER"]
    )
    # Replace version name with our semver version
    set_info_plist_value(
      path: "Runner/Info.plist",
      key: "CFBundleShortVersionString",
      value: ENV["BUILD_NAME"]
    )
    # Run a first Flutter build with code signing disabled
    Dir.chdir "../.." do
      sh("flutter", "packages", "get")
      sh("flutter", "clean")
      sh("flutter", "build", "ios", "--release", "--no-codesign")
    end
    # Run a second Flutter build with custom code signing
    build_app(
        workspace: "Runner.xcworkspace",
        scheme: "Runner",
        configuration: "Release",
        xcargs: "-allowProvisioningUpdates",
        export_options: {
            signingStyle: "manual",
            method: "app-store",
            provisioningProfiles: {
                "#{ENV["IOS_APP_ID"]}": "match AppStore #{ENV["IOS_APP_ID"]}"
            }
        },
    )
    # Upload our build to TestFlight (Beta track)
    upload_to_testflight(
        skip_waiting_for_build_processing: true,
        apple_id: "123456789"
    )
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, let's decrypt this file step-by-step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;before_all&lt;/code&gt; : Before all steps run we create a local keychain that will later store iOS profile and certificates for app signting. This keychain will only live the time of the Codemagic build so we don't really care what the password is.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;match&lt;/code&gt; : We start the &lt;em&gt;beta&lt;/em&gt; lane with profile and certificates synchronization. Codemagic configuration for iOS deployment can be rather complicated, in order to avoid this complexity by handling certificates and provisioning profiles ourselves, we use Fastlane and &lt;a href="https://docs.fastlane.tools/actions/match/" rel="noopener noreferrer"&gt;match&lt;/a&gt; for &lt;a href="https://codesigning.guide/" rel="noopener noreferrer"&gt;code signing&lt;/a&gt;. Here we basically tell &lt;a href="https://docs.fastlane.tools/actions/match/" rel="noopener noreferrer"&gt;match&lt;/a&gt; to retrieve all stored profiles and certificates from the repo we stored them previously, and store them into the keychain we created in the previous step. We tell match to get things for &lt;code&gt;appstore&lt;/code&gt; type, i.e. release profile and certificates. We will see later on in iOS setup how to initialize &lt;a href="https://docs.fastlane.tools/actions/match/" rel="noopener noreferrer"&gt;match&lt;/a&gt; to create and store profile and certificates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;update_code_signing_settings&lt;/code&gt; : We disable automatic code signing because we want to handle it manually using match-retrieved profile/certificates from previous step&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;update_project_provisioning&lt;/code&gt; : This step is essential as it tells Xcode to use our generated profile and certificates instead of its default (automatic) signing files. We tell it to use &lt;a href="https://docs.fastlane.tools/actions/match/" rel="noopener noreferrer"&gt;match retrieved provisioning profile&lt;/a&gt; instead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;set_info_plist_value&lt;/code&gt; : We replace Xcode version values with the correct version:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PROJECT_BUILD_NUMBER&lt;/code&gt; is the &lt;a href="https://docs.codemagic.io/building/environment-variables/" rel="noopener noreferrer"&gt;Codemagic global build number&lt;/a&gt; (global to your application, no matter which workflow you run). Useful to deploy the same version multiple times and from different workflows.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BUILD_NAME&lt;/code&gt; is the actual version of our project, in semver terminology. Note that it is up to you to handle the bump of this variable on each of your app's releases. In my case I use Gitlab pipeine with a custom script to automatically bump &lt;code&gt;BUILD_NAME&lt;/code&gt; environment variable inside &lt;code&gt;codemagic.yaml&lt;/code&gt; and commit this as a release commit.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flutter build ios --release --no-codesign&lt;/code&gt; : Flutter setup with a full packages/clean and build process, but with codesign disabled. As stated in &lt;a href="https://flutter.dev/docs/deployment/cd#cloud-build-and-deploy-setup" rel="noopener noreferrer"&gt;Flutter official CI/CD documentation&lt;/a&gt;, we need to initialize our project wiht a first Flutter build before we can actually run the signing build step with Fastlane (see next step)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;build_app&lt;/code&gt; : We use &lt;a href="https://docs.fastlane.tools/" rel="noopener noreferrer"&gt;Fastlane&lt;/a&gt; once again to build a signed version of our iOS application. Of course we tell it to use the provisioning profile we retrieved in previous step with match.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;upload_to_testflight&lt;/code&gt; : We can finally use &lt;a href="https://docs.fastlane.tools/actions/upload_to_testflight/" rel="noopener noreferrer"&gt;Fastlane upload_to_testflight task&lt;/a&gt; to automatically push our new &lt;code&gt;.ipa&lt;/code&gt; application to Apple TestFlight (for beta testing). The two parameters are set to avoid waiting for Apple build processing to end Codemagic build. It means that you are not 100% sure that your build has correctly been received (and is valid) by Apple but in the other hand it has 2 great advantages:

&lt;ul&gt;
&lt;li&gt;It consumes less Codemagic free minutes waiting for an external response that is not really part of the build itself&lt;/li&gt;
&lt;li&gt;It is the only way of only using an &lt;a href="https://docs.fastlane.tools/actions/upload_to_testflight/#use-an-application-specific-password-to-upload" rel="noopener noreferrer"&gt;Application Specific Password&lt;/a&gt; to push your app. Otherwise you would need 2FA with a short-lived session token, and you don't want that on your CI. Really, believe me 🙄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  🤖 Android setup &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Codemagic configuration for Android deployment is pretty straightforward and is rather well described in code signing documentations of both &lt;a href="https://flutter.dev/docs/deployment/android#signing-the-app" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt; and &lt;a href="https://docs.codemagic.io/code-signing/android-code-signing/" rel="noopener noreferrer"&gt;Codemagic&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will need to create a few things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a service account key to interact with &lt;a href="https://play.google.com/console" rel="noopener noreferrer"&gt;Google Play Console&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a keystore for Android app signing&lt;/li&gt;
&lt;li&gt;Configure Android project to use code signing for release mode, with appropriate keystore&lt;/li&gt;
&lt;li&gt;Locally generate a signed Android application and upload this first release to Google Play Console&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Google Play service account key  &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Let's start by creating the service account key.&lt;br&gt;
These steps are very well described in &lt;a href="https://docs.fastlane.tools/actions/supply/#setup" rel="noopener noreferrer"&gt;Codemagic documentation&lt;/a&gt; (in Google Play section).&lt;/p&gt;

&lt;p&gt;This variable is going to be translated into Android &lt;code&gt;google-play-store.json&lt;/code&gt; by our &lt;code&gt;codemagic/post-clone.sh&lt;/code&gt; script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GOOGLE_PLAY_STORE_JSON_KEY_PATH#&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FCI_BUILD_DIR&lt;/span&gt;&lt;span class="s2"&gt;/android/app/google-play-store.json"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$GOOGLE_PLAY_STORE_JSON_BASE64&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$GOOGLE_PLAY_STORE_JSON_KEY_PATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...which will used by Fastlane further down the road when deploying our application to Google Play Console.&lt;/p&gt;

&lt;p&gt;⚠️ Only the app owner of Google Play Console project is able to create a service account key, make sure you ask this person to create (and send you) the JSON key !&lt;/p&gt;

&lt;p&gt;We now need to create a file named &lt;code&gt;android/fastlane/Appfile&lt;/code&gt; to give Fastslane our Google Play service key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;json_key_file&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"app/google-play-store.json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
package_name&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"com.company.awesome_app"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, you should put your Android package name here (lowercase letters and underscores only !)&lt;/p&gt;

&lt;p&gt;ℹ️ the actual JSON key will be populated later on by &lt;code&gt;post-clone.sh&lt;/code&gt; script&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Keystore
&lt;/h2&gt;

&lt;p&gt;Generate a keystore for current project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;keytool &lt;span class="nt"&gt;-genkey&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-keystore&lt;/span&gt; your_app.jks &lt;span class="nt"&gt;-alias&lt;/span&gt; your_app_key &lt;span class="nt"&gt;-keyalg&lt;/span&gt; RSA &lt;span class="nt"&gt;-keysize&lt;/span&gt; 2048 &lt;span class="nt"&gt;-validity&lt;/span&gt; 10000 &lt;span class="nt"&gt;-storetype&lt;/span&gt; JKS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will prompted for a password.&lt;/p&gt;

&lt;p&gt;⚠️ Don't forget to securely store the password and share it with your team if they need to sign the app using the keystore (e.g. for CI builds). 🔐&lt;/p&gt;

&lt;p&gt;ℹ️ &lt;code&gt;-storetype JKS&lt;/code&gt; option is required for Java 9+ otherwise you will not be prompted for alias key/password and the keystore will not be compatible for code signing anyway...&lt;/p&gt;

&lt;p&gt;Now that you have an Android keystore, you need to encode your keystore to base64.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; your_app.jks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Project configuration
&lt;/h2&gt;

&lt;p&gt;Now that we have created util stuff needed to sign and publish our app to Google Play, we only need to tweak our configuration to reference info.&lt;/p&gt;

&lt;p&gt;First open &lt;code&gt;codemagic.yaml&lt;/code&gt; and define the following variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GOOGLE_PLAY_STORE_JSON_BASE64&lt;/code&gt; : &lt;em&gt;[Codemagic-encrypted]&lt;/em&gt; - base64 encoded version of JSON key downloaded from Google Play Console in step 1 above.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ANDROID_KEYSTORE&lt;/code&gt; : &lt;em&gt;[Codemagic-encrypted]&lt;/em&gt; - base64 encoded version of the keystore generated in step 2 above.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ANDROID_KEYSTORE_PASSWORD&lt;/code&gt; : &lt;em&gt;[Codemagic-encrypted]&lt;/em&gt; - password of the generated store&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ANDROID_KEY_ALIAS&lt;/code&gt; : &lt;em&gt;[Codemagic-encrypted]&lt;/em&gt; - alias of the generated key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ANDROID_KEY_PASSWORD&lt;/code&gt; : &lt;em&gt;[Codemagic-encrypted]&lt;/em&gt; - password of the generated key alias&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ Variables that are marked as &lt;em&gt;[Codemagic-encrypted]&lt;/em&gt; must be encrypted using Codemagic user interface.&lt;br&gt;
Go to &lt;code&gt;any project&lt;/code&gt; &amp;gt; &lt;code&gt;configuration wheel&lt;/code&gt; &amp;gt; &lt;code&gt;Encrypt environment variable&lt;/code&gt; and paste the variable you want to encrypt, and copy it back to &lt;code&gt;codemagic.yaml&lt;/code&gt;.&lt;br&gt;
Keep the &lt;code&gt;Encrypted(VAR)&lt;/code&gt; around your variable !&lt;/p&gt;

&lt;p&gt;These variables are going to be translated into Android &lt;code&gt;key.properties&lt;/code&gt; by our &lt;code&gt;codemagic/post-clone.sh&lt;/code&gt; script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$ANDROID_KEYSTORE&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/keystore.keystore
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FCI_BUILD_DIR&lt;/span&gt;&lt;span class="s2"&gt;/android/key.properties"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
storePassword#&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_KEYSTORE_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;
keyAlias=&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_KEY_ALIAS&lt;/span&gt;&lt;span class="sh"&gt;
keyPassword=&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_KEY_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;
storeFile=/tmp/keystore.keystore
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can configure our &lt;code&gt;android/app/build.gradle&lt;/code&gt; to sign our app using provided keystore. First add these lines above &lt;code&gt;android&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;def keystoreProperties &lt;span class="o"&gt;=&lt;/span&gt; new Properties&lt;span class="o"&gt;()&lt;/span&gt;
def keystorePropertiesFile &lt;span class="o"&gt;=&lt;/span&gt; rootProject.file&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key.properties'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;keystorePropertiesFile.exists&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    keystoreProperties.load&lt;span class="o"&gt;(&lt;/span&gt;new FileInputStream&lt;span class="o"&gt;(&lt;/span&gt;keystorePropertiesFile&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These lines tell Android to read keystore properties from our specific file. We can now add signing configs to the build (replace existing lines with these new ones):&lt;br&gt;
&lt;/p&gt;

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

signingConfigs &lt;span class="o"&gt;{&lt;/span&gt;
    release &lt;span class="o"&gt;{&lt;/span&gt;
        keyAlias keystoreProperties[&lt;span class="s1"&gt;'keyAlias'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
        keyPassword keystoreProperties[&lt;span class="s1"&gt;'keyPassword'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
        storeFile keystoreProperties[&lt;span class="s1"&gt;'storeFile'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; ? file&lt;span class="o"&gt;(&lt;/span&gt;keystoreProperties[&lt;span class="s1"&gt;'storeFile'&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; : null
        storePassword keystoreProperties[&lt;span class="s1"&gt;'storePassword'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

buildTypes &lt;span class="o"&gt;{&lt;/span&gt;
    release &lt;span class="o"&gt;{&lt;/span&gt;
        signingConfig signingConfigs.release
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're ready to sign your Android app !&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Signed release &amp;amp; first upload
&lt;/h2&gt;

&lt;p&gt;In order to push automated releases of your Android app, you need to create a first release manually on the &lt;a href="https://play.google.com/console/" rel="noopener noreferrer"&gt;Google Play Console&lt;/a&gt; interface and upload a first signed bundle/apk.&lt;/p&gt;

&lt;p&gt;You can follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a file named &lt;code&gt;/android/key.properties&lt;/code&gt; with the following content:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;storePassword&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_KEYSTORE_PASSWORD&lt;/span&gt;
&lt;span class="nv"&gt;keyAlias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_KEY_ALIAS&lt;/span&gt;
&lt;span class="nv"&gt;keyPassword&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$ANDROID_KEY_PASSWORD&lt;/span&gt;
&lt;span class="nv"&gt;storeFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/keystore.keystore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obvisouly you need to replace the values of each property with the actual value you generated (same values as you set for corresponding environment variable in &lt;em&gt;Project Configuration&lt;/em&gt; section).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make sure &lt;code&gt;/android/key.properties&lt;/code&gt; is excluded in your &lt;code&gt;/android/.gitignore&lt;/code&gt;. Flutter does this by default when creating the app, you should see the following line:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;key.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't have this line, obviously add it, it is crucial that you do not push secrets to your distant Git repository !&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build and package the application with signing enabled (release mode):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter build appbundle &lt;span class="nt"&gt;--build-number&lt;/span&gt; 1 &lt;span class="nt"&gt;--build-name&lt;/span&gt; 0.0.1 &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the signed application is ready, it's time to upload it to Google Play Console: create a new release under "Internal tests" and drop your appbundle there, you should be able to continue with your brand new release.&lt;/p&gt;

&lt;p&gt;Once your first release is accessible for internal tests, you can now upload automated releases through CI ! Yeah 🎉&lt;/p&gt;

&lt;p&gt;⚠️ Make sure you filled all necessary information on Google Play Console for internal tests (Dashboard will tell you what steps you need to complete first).&lt;/p&gt;

&lt;h1&gt;
  
  
  🍏📱 iOS setup &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Codemagic configuration for iOS deployment can be rather complicated.&lt;br&gt;
To avoid handling certificates and provisioning profiles ourselves, we'll be using Fastlane and &lt;a href="https://docs.fastlane.tools/actions/match/" rel="noopener noreferrer"&gt;match&lt;/a&gt; for &lt;a href="https://codesigning.guide/" rel="noopener noreferrer"&gt;code signing&lt;/a&gt;, as explained in Prerequisites.&lt;/p&gt;

&lt;p&gt;First make sure you use latest Fastlane version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;fastlane
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also create an empty Git repository called &lt;code&gt;certificates&lt;/code&gt; somewhere in your Gitlab/Github/Bitbucket. We can no run a few commands to setup codesigning for &lt;code&gt;iOS&lt;/code&gt; so make sure you are in &lt;code&gt;ios&lt;/code&gt; subdirectory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;ios/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time to init match to point to our certificate repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;match init
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Select git
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; URL of the Git repo:
git@gitlab.com:your-company/your-app-group-folder/certificates.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should generate a Matchfile in &lt;code&gt;.fastlane/Matchfile&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git_url&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"git@gitlab.com:your-company/your-app-group-folder/certificates.git"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

storage_mode&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"git"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"development"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# The default type, can be: appstore, adhoc, enterprise or development&lt;/span&gt;

&lt;span class="c"&gt;# app_identifier(["tools.fastlane.app", "tools.fastlane.app2"])&lt;/span&gt;
&lt;span class="c"&gt;# username("user@fastlane.tools") # Your Apple Developer Portal username&lt;/span&gt;

&lt;span class="c"&gt;# For all available options run `fastlane match --help`&lt;/span&gt;
&lt;span class="c"&gt;# Remove the # in the beginning of the line to enable the other options&lt;/span&gt;

&lt;span class="c"&gt;# The docs are available on https://docs.fastlane.tools/actions/match&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Use a SSH url for the repository (rather than the HTTPS url) as we will use an SSH key later to enable Codemagic fetching our repository.&lt;/p&gt;

&lt;p&gt;Now we have to create &lt;code&gt;ios/fatslane/Appfile&lt;/code&gt; to fill out a few needed information for Fastlane (and match too):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;app_identifier&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"com.company.awesomeApp"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# The bundle identifier of your app&lt;/span&gt;
apple_id&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"apple-technical@your-company.com"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# Your Apple email address for the technical account&lt;/span&gt;

team_id&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"123ABC1DE"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# Developer Portal Team ID&lt;/span&gt;
itc_team_id&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"123456789"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# App Store Connect Team ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ To easily output these variables you can run &lt;code&gt;fastlane produce&lt;/code&gt; and you should get all these variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app_identifier&lt;/code&gt; is your bundle id as created in Prerequisites - Config and can be found in your [Apple identifier](&lt;a href="https://developer.apple.com/account/resources/identifiers/bundleId" rel="noopener noreferrer"&gt;https://developer.apple.com/account/resources/identifiers/bundleId&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apple_id&lt;/code&gt; is the technical account email address (e.g. &lt;a href="mailto:apple-technical@your-company.com"&gt;apple-technical@your-company.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;team_id&lt;/code&gt; is your Developer portal team ID and can be found in your &lt;a href="https://developer.apple.com/account/resources/identifiers/bundleId" rel="noopener noreferrer"&gt;Apple identifier&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;itc_team_id&lt;/code&gt; is an integer team id derived from above &lt;code&gt;team_id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once all variables are set, we can use match to create new certificates and profiles.&lt;br&gt;
Please make sure that the Apple account you set in &lt;code&gt;apple_id&lt;/code&gt; above has "App manager" permissions and not just "Developer" or you won't have permissions to create certificates and profiles.&lt;/p&gt;

&lt;p&gt;Let's run the command to generate everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fastlane match appstore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter password for &lt;code&gt;apple-technical@your-company.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the command executed successfully you should now have new, generated &lt;a href="https://developer.apple.com/account/resources/certificates/list" rel="noopener noreferrer"&gt;certificates&lt;/a&gt; and [profiles]&lt;a href="https://developer.apple.com/account/resources/profiles/list" rel="noopener noreferrer"&gt;https://developer.apple.com/account/resources/profiles/list&lt;/a&gt;).&lt;br&gt;
Also these certificates should now be stored encrypted into your Git certificates repository.&lt;/p&gt;

&lt;p&gt;⚠️ Don't forget to securely store &lt;code&gt;ios keychain&lt;/code&gt; and &lt;code&gt;match&lt;/code&gt; passwords and share them with your team if they need to regenerate certificates ! 🔐&lt;/p&gt;

&lt;p&gt;Now that you have generated profile and certificate, it's time to complete &lt;a href="https://dev.tocodemagic-setup"&gt;Codemagic workflow&lt;/a&gt; by setting all environment variables such as &lt;a href="https://docs.fastlane.tools/best-practices/continuous-integration/#environment-variables-to-set" rel="noopener noreferrer"&gt;Fastlane environment variables&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BUILD_NAME&lt;/code&gt; : The version that you wish to deploy, in semantic versioning notation (e.g. 1.2.3, or more generally X.Y.Z)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IOS_APP_ID&lt;/code&gt; : the app identifier as set in &lt;a href="https://developer.apple.com/account/resources/identifiers/list" rel="noopener noreferrer"&gt;Certificates, Identifiers &amp;amp; Profiles&lt;/a&gt;.

&lt;ul&gt;
&lt;li&gt;Example: com.company.awesomeApp&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;APPLE_DEVELOPER_TEAM_ID&lt;/code&gt; : Apple Developer Team Id (e.g. 123ABC4DE)&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;FASTLANE_EMAIL&lt;/code&gt; : Apple Developer account email (e.g. &lt;a href="mailto:apple-technical@your-company.com"&gt;&lt;/a&gt;&lt;a href="mailto:apple-technical@your-company.com"&gt;apple-technical@your-company.com&lt;/a&gt;)&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;FASTLANE_PASSWORD&lt;/code&gt; : Apple Developer account password&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;MATCH_PASSWORD&lt;/code&gt; : &lt;em&gt;[Codemagic-encrypted]&lt;/em&gt; - Password used when generating &lt;code&gt;fastlane match appstore&lt;/code&gt; above using &lt;code&gt;match&lt;/code&gt;.&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;SSH_KEY_FOR_FASTLANE_MATCH_BASE64&lt;/code&gt; : &lt;em&gt;[Codemagic-encrypted]&lt;/em&gt; - Base64 encoded private SSH key of Codemagic user for Gitlab. You can do the following to generate SSH keys :

&lt;ul&gt;
&lt;li&gt;Generate a new SSH key pair : &lt;code&gt;ssh-keygen -t ed25519 -C "SSH key description"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copy public key and add it under &lt;a href="https://gitlab.com/-/profile/keys" rel="noopener noreferrer"&gt;Gitlab's SSH keys&lt;/a&gt; for Codemagic user.&lt;/li&gt;
&lt;li&gt;Copy private key and encrypt it using Codemagic (see note below)&lt;/li&gt;
&lt;li&gt;Delete SSH key from your local computer&lt;/li&gt;
&lt;li&gt;If you ever need to see the key... you can't, just revoke the old one and generate a new pair !&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD&lt;/code&gt; : &lt;em&gt;[Codemagic-encrypted]&lt;/em&gt; - Apple Application specific password as described in &lt;a href="https://docs.fastlane.tools/best-practices/continuous-integration/#application-specific-passwords" rel="noopener noreferrer"&gt;Fastlane documentation&lt;/a&gt;. You can follow these steps:

&lt;ul&gt;
&lt;li&gt;Go to &lt;a href="http://appleid.apple.com/account/manage" rel="noopener noreferrer"&gt;Apple manage account page&lt;/a&gt; with your Apple account corresponding to &lt;code&gt;FASTLANE_EMAIL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Generate a new application specific password&lt;/li&gt;
&lt;li&gt;Encrypt the password using Codemagic (see note below)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;ℹ️ You can run &lt;code&gt;fastlane produce&lt;/code&gt; to output some of these information (app_identifier, team_id, itc_team_id).&lt;/p&gt;

&lt;p&gt;⚠️ Variables that are marked as &lt;em&gt;[Codemagic-encrypted]&lt;/em&gt; must be encrypted using Codemagic user interface.&lt;br&gt;
Go to &lt;code&gt;any project&lt;/code&gt; &amp;gt; ⚙️ &amp;gt; &lt;code&gt;Encrypt environment variable&lt;/code&gt; and paste the variable you want to encrypt, and copy it back to &lt;code&gt;codemagic.yaml&lt;/code&gt;.&lt;br&gt;
Keep the &lt;code&gt;Encrypted(VAR)&lt;/code&gt; around your variable !&lt;/p&gt;

&lt;h2&gt;
  
  
  iOS Project configuration
&lt;/h2&gt;

&lt;p&gt;Just a few more things we need to setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;code&gt;ios/Runner.xcodeproj/project.pbxproj&lt;/code&gt;, set all &lt;code&gt;DEVELOPMENT_TEAM&lt;/code&gt; variables to your actual Apple Development team (must be the same as &lt;code&gt;APPLE_DEVELOPER_TEAM_ID&lt;/code&gt; env var above). If development team variables do not appear in &lt;code&gt;ios/Runner.xcodeproj/project.pbxproj&lt;/code&gt; you can setup manually by doing following steps:

&lt;ul&gt;
&lt;li&gt;Open Xcode&lt;/li&gt;
&lt;li&gt;Select Runner in left pannel&lt;/li&gt;
&lt;li&gt;In central panel select Targets &amp;gt; Runner&lt;/li&gt;
&lt;li&gt;Open "Signing &amp;amp; Capabilities" tab&lt;/li&gt;
&lt;li&gt;Under "All" sub-tab, disable automatic code siging and select your team from the appropriate field.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;If no team is found you can also import a provisioning profile after downloading it from &lt;a href="https://developer.apple.com/account/resources/profiles" rel="noopener noreferrer"&gt;Apple profiles&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In &lt;code&gt;ios/Runner/Runner.entitlements&lt;/code&gt; (create the file if you don't have it), setup required &lt;a href="https://developer.apple.com/documentation/bundleresources/entitlements" rel="noopener noreferrer"&gt;Apple entitlements&lt;/a&gt; for your application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you don't use any entitlements, you should still declare it by leaving the file with an empty list:
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;plist&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;dict/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Phew, you should be all set for iOS automated deployment 🎉&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;As you can see setting up CI/CD automation for Flutter projects to go from the ground up to deployed beta versions of both Android and iOS applications requires a bit of patience, but in the end once you understand the various components interacting with each other, it's almost magic ! 🧙‍♀️&lt;/p&gt;

&lt;p&gt;My advice : take the time to set up CI/CD when your Flutter project starts, you will be grateful to have it all automated when comes the mandatory rush of your project 🙂&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>codemagic</category>
      <category>fastlane</category>
      <category>ci</category>
    </item>
    <item>
      <title>Flutter UTF8 TextField length limiter and char counter</title>
      <dc:creator>Olivier Revial</dc:creator>
      <pubDate>Fri, 23 Oct 2020 13:34:23 +0000</pubDate>
      <link>https://dev.to/stack-labs/flutter-utf8-textfield-length-limiter-and-char-counter-31o7</link>
      <guid>https://dev.to/stack-labs/flutter-utf8-textfield-length-limiter-and-char-counter-31o7</guid>
      <description>&lt;h2&gt;
  
  
  Flutter &lt;code&gt;TextField&lt;/code&gt; is awesome, but...
&lt;/h2&gt;

&lt;p&gt;I love Flutter and when it comes to &lt;code&gt;TextField&lt;/code&gt;s, it's pretty easy to enforce a maximum length and have a counter display that indicates the current number of characters and the maximum length. All you have to do is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;maxLength:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you get a beautiful char-limited text field:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1vq5xo0mufzmwlzs7seh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1vq5xo0mufzmwlzs7seh.png" width="345" height="82"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty easy, right ?&lt;/p&gt;

&lt;p&gt;Not so fast !&lt;/p&gt;

&lt;p&gt;What happens if you are using special characters such as emojis, accent chars or graphenes ? Let's see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhbib25aozrzetms3p1qs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhbib25aozrzetms3p1qs.png" alt="Capture d’écran 2020-10-05 à 12.50.24" width="345" height="82"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hmm 🤔, your text is 15 characters long, what's the problem ?&lt;/p&gt;

&lt;p&gt;Well... sometimes you don't want to limit text length to visible characters but you want to limit length to the actual number of bytes of your text. For example, if you are sending text to a Bluetooth device or a fixed-length field in a database, you need to make sure that you won't send too many UTF-8 encoded characters. And here is the problem my friends: while a letter is coded on one byte, &lt;a href="https://pusher.com/tutorials/styled-text-flutter#unicode" rel="noopener noreferrer"&gt;special characters and emojis can be coded on 2 to 8 bytes&lt;/a&gt; !&lt;/p&gt;

&lt;p&gt;In this case, above example "I ❤️ Flutter 😻😍🤩" would actually take 29 bytes when UTF-8 encoded... a bit more than the 15 displayed by Flutter counter. &lt;/p&gt;

&lt;p&gt;And by the way, Flutter &lt;a href="https://api.flutter.dev/flutter/material/TextField/maxLength.html" rel="noopener noreferrer"&gt;official &lt;code&gt;TextField&lt;/code&gt; documentation&lt;/a&gt; is pretty clear on this exact issue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Limitations&lt;br&gt;
The text field does not currently count Unicode grapheme clusters (i.e. characters visible to the user), it counts Unicode scalar values, which leaves out a number of useful possible characters (like many emoji and composed characters), so this will be inaccurate in the presence of those characters. If you expect to encounter these kinds of characters, be generous in the maxLength used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ...we need an UTF-8 length limiter...
&lt;/h2&gt;

&lt;p&gt;When reading documentation above it becomes clear that we need our own implementation of &lt;code&gt;TextField&lt;/code&gt; length limiter. Fortunately for us, this is pretty easy to do as &lt;code&gt;TextField&lt;/code&gt; has a param called &lt;code&gt;inputFormatters&lt;/code&gt; that takes a list of input formatters:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Optional input validation and formatting overrides.&lt;/p&gt;

&lt;p&gt;Formatters are run in the provided order when the text input changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By the way Flutter default text length limiter works by using a specific input formatter called &lt;a href="https://api.flutter.dev/flutter/services/LengthLimitingTextInputFormatter-class.html" rel="noopener noreferrer"&gt;LengthLimitingTextInputFormatter&lt;/a&gt;, so now we will just mimic its behavior to get our very own length limiter that will limit on bytes count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_Utf8LengthLimitingTextInputFormatter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;TextInputFormatter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;_Utf8LengthLimitingTextInputFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxLength&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;maxLength&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;maxLength&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;TextEditingValue&lt;/span&gt; &lt;span class="n"&gt;formatEditUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;TextEditingValue&lt;/span&gt; &lt;span class="n"&gt;oldValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;TextEditingValue&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxLength&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;maxLength&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="n"&gt;bytesLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// If already at the maximum and tried to enter even more, keep the old value.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytesLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oldValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;oldValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;TextEditingValue&lt;/span&gt; &lt;span class="n"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextEditingValue&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytesLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;characters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;takeWhile&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;nbBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bytesLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nbBytes&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;newValue&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;nbBytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TextEditingValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;selection:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;baseOffset:&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;extentOffset:&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;composing:&lt;/span&gt; &lt;span class="n"&gt;TextRange&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;bytesLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class is pretty simple : it checks whether we are exceeding maximum length (see &lt;code&gt;formatEditUpdate&lt;/code&gt; method). If we already are at maximum bytes length and try to type in another char, this char will simply be rejected. In case we suddenly exceed the char limit with more than 1 char (e.g. if multiple chars are being pasted to the text field), we will call &lt;code&gt;truncate&lt;/code&gt; method to get our string back to &lt;code&gt;maxLength&lt;/code&gt; bytes. &lt;br&gt;
Also note the important method here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;bytesLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method simply returns the length of a String in bytes rather than in Unicode characters as done in Flutter default's length limiter.&lt;/p&gt;

&lt;p&gt;Great, let's use this new input formatter in our &lt;code&gt;TextField&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;maxLength:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;inputFormatters:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
     &lt;span class="n"&gt;_Utf8LengthLimitingTextInputFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;NOTE&lt;/em&gt;: In this example length limiter class must be in the same file as your widget because I declared the formatter class private to the current file.&lt;/p&gt;

&lt;p&gt;All good ? 🤔 Hum, actually if we test now our text field input will be effectively limited to 20 (or N) &lt;strong&gt;bytes&lt;/strong&gt; and we won't be able to enter a new "character" (letter, emoji, etc) if this new character would make the total bytes length exceed our limit. So even if you are currently at 18/20, you won't be able to type in an awesome 4-bytes emoji 🥳. However, the counter is still &lt;code&gt;TextField&lt;/code&gt;'s default counter so in my previous example I will be blocked but the counter will be wrong, as shown in the capture below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3jo4ln7jy3fe4qc3yc4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3jo4ln7jy3fe4qc3yc4u.png" alt="Capture d’écran 2020-10-05 à 15.26.47" width="357" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Counter should actually be at 15 because I entered 15 bytes worth of data with the heart emoji coded as 4 bytes&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let's customize Flutter &lt;code&gt;TextField&lt;/code&gt; char counter !&lt;/p&gt;

&lt;h2&gt;
  
  
  ...and a custom chars counter
&lt;/h2&gt;

&lt;p&gt;To display the correct number of bytes typed in the text input, we need to override default char counter and make sure it's displayed every time a character is typed, as the standard char counter would do.&lt;/p&gt;

&lt;p&gt;To do this we can use &lt;a href="https://api.flutter.dev/flutter/material/TextField/buildCounter.html" rel="noopener noreferrer"&gt;&lt;code&gt;buildCounter&lt;/code&gt;&lt;/a&gt; of &lt;code&gt;TextField&lt;/code&gt; class and return our own widget. By default the builder callback takes a parameter named &lt;code&gt;currentLength&lt;/code&gt; which is the wrong length, so we will ignore this parameter and calculate string bytes length using UTF-8 encoding instead. To do this, we need a reference to the current value of our &lt;code&gt;TextField&lt;/code&gt;, so let's create a &lt;code&gt;TextEditingController&lt;/code&gt; in our widget state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;TextEditingController&lt;/span&gt; &lt;span class="n"&gt;_utf8TextController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextEditingController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's set the &lt;code&gt;buildCounter&lt;/code&gt; callback that uses our text controller to check the UTF-8 bytes length every time the user enters/pastes some text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TextField(
  controller: _utf8TextController,
  maxLength: 20,
  buildCounter: (
    context, { currentLength, isFocused, maxLength }) {
      int utf8Length = utf8.encode(_utf8TextController.text).length;
      return Container(
        child: Text(
          '$utf8Length/$maxLength',
          style: Theme.of(context).textTheme.caption,
          ),
        );
    },
 inputFormatters: [              
   _Utf8LengthLimitingTextInputFormatter(maxLength),            
 ],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voilà 🥳 ! We now have a beautiful text field input limited to 20 &lt;strong&gt;bytes&lt;/strong&gt;, not characters... whatever that means.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjjlug4y9v5kswy6ecf26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjjlug4y9v5kswy6ecf26.png" alt="Capture d’écran 2020-10-05 à 15.41.54" width="353" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Notes&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I added a &lt;code&gt;caption&lt;/code&gt; style to the counter to mimic counter original look.&lt;/li&gt;
&lt;li&gt;This technique would obviously work with &lt;code&gt;CupertinoTextField&lt;/code&gt; as well as it presents exactly the same parameters.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Full code samples are available in this &lt;a href="https://github.com/orevial/flutter-text-field-utf8-length-limiter-demo" rel="noopener noreferrer"&gt;Github demo repo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
    </item>
    <item>
      <title>Native images with Micronaut and GraalVM</title>
      <dc:creator>Olivier Revial</dc:creator>
      <pubDate>Mon, 01 Jun 2020 13:04:43 +0000</pubDate>
      <link>https://dev.to/stack-labs/native-images-with-micronaut-and-graalvm-4koe</link>
      <guid>https://dev.to/stack-labs/native-images-with-micronaut-and-graalvm-4koe</guid>
      <description>&lt;p&gt;After a &lt;a href="https://dev.to/stack-labs/a-micronaut-introduction-4d9l"&gt;Micronaut introduction&lt;/a&gt; we saw in a previous article how we can create a &lt;a href="https://dev.to/stack-labs/cli-applications-with-micronaut-and-picocli-14eo"&gt;simple and powerful CLI application&lt;/a&gt; using Micronaut and Picocli, but our application was too slow for a CLI... it's time to take it to the next level by GraalVM native compilation support to this app to run a very (very) fast CLI application 🏎 !    &lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding native compilation
&lt;/h2&gt;

&lt;p&gt;If you are used to the JVM environment, you might be wondering what native compilation is and how it differs from JVM compilation. Let's forget Micronaut for a while and focus on this.&lt;/p&gt;

&lt;h3&gt;
  
  
  JVM compilation
&lt;/h3&gt;

&lt;p&gt;When we compile a JVM application (say a Java or Kotlin app for example), we usually package it as a JAR (&lt;strong&gt;J&lt;/strong&gt;ava &lt;strong&gt;AR&lt;/strong&gt;chive), which is made of "bytecode", our well-known ".class" files. In order to run a Java application we have to run it on a &lt;strong&gt;J&lt;/strong&gt;ava &lt;strong&gt;V&lt;/strong&gt;irtual &lt;strong&gt;M&lt;/strong&gt;achine, i.e. a virtual machine that has been specifically developed to interpret our Java bytecode. The great thing about the JVM is that JVMs exist for all systems (Unix, Mac, Windows), meaning that as a developer you only need to care about compiling your app to a jar, and then the JVM will run it, no matter which system you're on. In short, the JVM idea comes from this 1995 slogan:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Write once, run anywhere&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can write and compile your application code on your Windows machine (using the Windows-compatible &lt;strong&gt;J&lt;/strong&gt;ava &lt;strong&gt;D&lt;/strong&gt;evelopment &lt;strong&gt;K&lt;/strong&gt;it) and deploy your jar to your Debian server (using the Unix-compatible &lt;strong&gt;J&lt;/strong&gt;ava &lt;strong&gt;R&lt;/strong&gt;untime &lt;strong&gt;E&lt;/strong&gt;nvironment).&lt;/p&gt;

&lt;p&gt;So far so good, isn't it ? Well, if it's working so well, why in earth do we need another compilation system for our apps ? Well, as always things are not black and white. JVM interoperability is great but comes at the cost: the cost of adding a layer between the operating system and the application: yes, you guessed it, the JVM itself. This cost is paid in terms of memory footprint and startup times. This is due to the JVM weight but also to the fact that most JVM applications heavily rely on runtime reflection, making the application startup slower and slower (and the memory footprint bigger and bigger) as the codebase grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native compilation
&lt;/h3&gt;

&lt;p&gt;Under this mysterious "native compilation" is actually a quite simple principle: you translate your source program into native code - i.e. code composed of instructions directly recognized by your CPU - using tools such as an assembler or a linker. And these are exactly the same tools used to make the "native applications" that run on your computer. The native code is therefore linked to a particular family of processors (and thus a family of operating systems) sharing the same set of instructions.&lt;br&gt;
&lt;code&gt;C&lt;/code&gt; language is a very good example of native compilation: when you run &lt;code&gt;gcc&lt;/code&gt; what you do is compiling your code into an &lt;code&gt;exe&lt;/code&gt; on Windows or an executable file on Unix systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; gcc &lt;span class="nt"&gt;-o&lt;/span&gt; helloworld helloworld.c
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lah&lt;/span&gt;                                                                    

.rwxr-xr-x   12k olivier 23 May 19:39  helloworld
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although the interoperability problem might be a concern, native compilation offers various advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;applications will start much faster&lt;/li&gt;
&lt;li&gt;applications will have a much lower memory footprint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also note that the interoperability becomes less of a problem with containers as we are now able to compile native code using a container (with a specific operating system) and run the native image in a very lightweight version of the same operating system (only basic system libs such as file or networks libs are needed).&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the difference so big ?
&lt;/h3&gt;

&lt;p&gt;Well, let's test it with this Hello World application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloWorld&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World !"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's first compile it and run it with the JVM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; javac HelloWorld.java
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="s2"&gt;"java HelloWorld"&lt;/span&gt;

Benchmark &lt;span class="c"&gt;#1: java HelloWorld&lt;/span&gt;
  Time &lt;span class="o"&gt;(&lt;/span&gt;mean ± σ&lt;span class="o"&gt;)&lt;/span&gt;:      92.9 ms ±   9.0 ms    &lt;span class="o"&gt;[&lt;/span&gt;User: 94.4 ms, System: 23.2 ms]
  Range &lt;span class="o"&gt;(&lt;/span&gt;min … max&lt;span class="o"&gt;)&lt;/span&gt;:    81.5 ms … 122.1 ms    29 runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now let's do the same compiling with &lt;a href="https://www.graalvm.org/" rel="noopener noreferrer"&gt;GraalVM&lt;/a&gt; (more on that later):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; native-image HelloWorld
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="s2"&gt;"./helloworld"&lt;/span&gt;

Benchmark &lt;span class="c"&gt;#1: ./helloworld&lt;/span&gt;
  Time &lt;span class="o"&gt;(&lt;/span&gt;mean ± σ&lt;span class="o"&gt;)&lt;/span&gt;:       5.8 ms ±  14.6 ms    &lt;span class="o"&gt;[&lt;/span&gt;User: 1.6 ms, System: 1.4 ms]
  Range &lt;span class="o"&gt;(&lt;/span&gt;min … max&lt;span class="o"&gt;)&lt;/span&gt;:     2.3 ms …  86.8 ms    33 runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, even the most simple Hello World starts more than 10 times faster when compiled natively 🏎 !&lt;/p&gt;

&lt;p&gt;ℹ️ I used &lt;a href="https://github.com/sharkdp/hyperfine" rel="noopener noreferrer"&gt;&lt;code&gt;hyperfine&lt;/code&gt;&lt;/a&gt; here instead of &lt;code&gt;time&lt;/code&gt; to run a benchmark on commands&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to run fast (and small) ?
&lt;/h3&gt;

&lt;p&gt;Right now you may be asking yourself whether you need to run an app in tenth of milliseconds instead of a second.&lt;/p&gt;

&lt;p&gt;I'll give the easy answer: it depends on your use case. If you are only deploying a few microservices a few times a month on big servers, you most probably don't care much about the speed, memory footprint or size of your application. On the other hand, there a few use cases where native images can be useful or even essential:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when deploying the app as a serverless function, time is of the essence as you want to reduce cold starts as much as possible.&lt;/li&gt;
&lt;li&gt;when developing a CLI application (.e.g &lt;a href="https://micronaut-projects.github.io/micronaut-picocli/latest/guide/" rel="noopener noreferrer"&gt;with Picocli&lt;/a&gt;), a very fast startup time is mandatory if you want to keep a good and enjoyable user experience.&lt;/li&gt;
&lt;li&gt;when you care about the size of your app and its memory footprint. This is particularly true when deploying dozens of microservices packaged into containers onto a small cluster: the smaller the better. Native image, as they do not need a full JVM to run, are not contained on ~300MB JVM containers but rather in ~50MB containers. &lt;/li&gt;
&lt;li&gt;when security is a concern. When it comes to security of your containerized applications, the less the better. As native images require less libraries to run (basically they just require a very basic system with only networking libs and such) instead of an heavy JVM, attack surface is reduced.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Micronaut and GraalVM
&lt;/h2&gt;

&lt;p&gt;Now that we know what native image compilation is, let's see how it works on a &lt;a href="https://github.com/orevial/micronaut-weather-cli/tree/picocli-app" rel="noopener noreferrer"&gt;real application&lt;/a&gt;. In this article we will reuse weather-cli application from &lt;a href="https://dev.to/stack-labs/cli-applications-with-micronaut-and-picocli-14eo"&gt;previous article&lt;/a&gt; and add it native compilation support to see the differences between a JVM application and a native binary.&lt;/p&gt;

&lt;p&gt;⁉️ What ?&lt;/p&gt;

&lt;p&gt;The app I'm talking about is a weather CLI application that uses &lt;a href="http://micronaut.io/" rel="noopener noreferrer"&gt;Micronaut&lt;/a&gt; and &lt;a href="https://picocli.info/" rel="noopener noreferrer"&gt;Picocli&lt;/a&gt; and interacts with &lt;a href="https://www.weatherbit.io/features" rel="noopener noreferrer"&gt;Weatherbit.io API&lt;/a&gt; to provide the following commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;weather&lt;/code&gt; command will return the current weather for a given location (city and country)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;forecast&lt;/code&gt; command will return a forecast for the next 1 to 16 days for a given location (city and country)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❓ Why Micronaut ? &lt;/p&gt;

&lt;p&gt;In a strict sense we don't need a specific framework to use GraalVM and compile an application down to a native binary. However &lt;a href="http://micronaut.io/" rel="noopener noreferrer"&gt;Micronaut&lt;/a&gt; provides powerful features &lt;strong&gt;and&lt;/strong&gt; out of the box support for native compilation ! That's the great thing about Micronaut : it doesn't use runtime reflection or proxies, making it an excellent candidate for compiling native images to run very (very) fast microservices ! &lt;/p&gt;

&lt;h3&gt;
  
  
  Repository
&lt;/h3&gt;

&lt;p&gt;Code sample for this article can be found in &lt;a href="https://github.com/orevial/micronaut-weather-cli/tree/graalvm-app" rel="noopener noreferrer"&gt;this Github repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring our application for native compilation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  GraalVM configuration
&lt;/h4&gt;

&lt;p&gt;As mentioned earlier we will reuse an existing (Java) Micronaut app and add it native image support. Note however that if we were to create our native app from scratch we could use Micronaut &lt;code&gt;mn&lt;/code&gt; command to generate our app skeleton to pre-configure our build with Picocli and GraalVM dependencies, as mentionned in our &lt;a href="https://dev.to/stack-labs/a-micronaut-introduction-4d9l"&gt;Micronaut 101 article&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; mn create-cli-app weather-cli &lt;span class="nt"&gt;--features&lt;/span&gt; graal-native-image,http-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting &lt;code&gt;build.gradle&lt;/code&gt; build file would have been similar to our previous build file expect for the added GraalVM dependency. &lt;/p&gt;

&lt;p&gt;➡️ Let's add GraalVM dependency and Micronaut specific graal support to our &lt;code&gt;build.gradle&lt;/code&gt; (in the &lt;code&gt;dependencies&lt;/code&gt; block)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dependencies &lt;span class="o"&gt;{&lt;/span&gt;
...
    annotationProcessor &lt;span class="s2"&gt;"io.micronaut:micronaut-graal"&lt;/span&gt;
    compileOnly &lt;span class="s2"&gt;"org.graalvm.nativeimage:svm"&lt;/span&gt;
...
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In reality adding &lt;code&gt;graal-native-image&lt;/code&gt; feature to Micronaut command-line generation did more than just adding dependencies to the build and also added some needed configuration files for native compilation. &lt;/p&gt;

&lt;p&gt;There is one file we need to add to our project because GraalVM needs it and will &lt;a href="https://www.graalvm.org/docs/reference-manual/native-image/#native-image-configuration" rel="noopener noreferrer"&gt;try to pick it up&lt;/a&gt;: &lt;code&gt;native-image.properties&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;➡️ Add a new file in a new directory under &lt;code&gt;src/main/resources/META-INF/native-image/{package.name}/{application-name}/native-image.properties&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: In our case the file must be at the path &lt;code&gt;src/main/resources/META-INF/native-image/weather.cli/weather-cli-application/native-image.properties&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;➡️ Add the following content to this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Args = -H:IncludeResources=logback.xml|application.yml|bootstrap.yml \
       -H:Name=weather-cli \
       -H:Class=weather.cli.WeatherCliCommand
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically we tell GraalVM to include some resources files to our compiled application, and we tell it where to find the main class once the app has been compiled.&lt;/p&gt;

&lt;p&gt;Alright, that's pretty much it for GraalVM configuration as far as Micronaut is concerned !&lt;/p&gt;

&lt;h4&gt;
  
  
  Picocli native configuration
&lt;/h4&gt;

&lt;p&gt;Although our Micronaut/GraalVM configuration is correct, if we try to compile our app using &lt;code&gt;native-image&lt;/code&gt; right now, it wouldn't compile (or would crash at runtime) because Picocli uses reflection at its core, however reflection is not possible on a GraalVM runtime application. In other words, we need a way to tell &lt;code&gt;native-image&lt;/code&gt; tool that our Picocli application uses certain classes using reflection and thus that the tool should include them in the native binary. &lt;/p&gt;

&lt;p&gt;GraalVM is able to read a file named &lt;code&gt;reflection-config.json&lt;/code&gt; - that should be under the same &lt;code&gt;META-INF&lt;/code&gt; directory as &lt;code&gt;native-image.properties&lt;/code&gt; - that tells it what classes in our app are usually loaded using reflection, to make sure GraalVM includes them at compile time, and is able to know what fields and methods it should include.&lt;/p&gt;

&lt;p&gt;The file for our app basically looks this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"weather.cli.commands.ForecastSubcommand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"methods"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"setNbDays"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameterTypes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"int"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameterTypes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"weather.cli.commands.LocalizedCommand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spec"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spec"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spec"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"methods"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"setCountry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameterTypes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"setCity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameterTypes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"java.lang.String"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameterTypes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"weather.cli.WeatherApplication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"methods"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameterTypes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"java.lang.String[]"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameterTypes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"weather.cli.commands.CurrentWeatherSubcommand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"methods"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameterTypes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;😱 Beautiful isn't it ?&lt;/p&gt;

&lt;p&gt;Well of course if you are as lazy as I am you are now sweating while realizing this file should be updated each time you had a Picocli-related class !&lt;/p&gt;

&lt;p&gt;But don't worry, Picocli provides a tool named &lt;a href="https://github.com/remkop/picocli/tree/master/picocli-codegen#gradle" rel="noopener noreferrer"&gt;picocli-codegen&lt;/a&gt; to automatically include this reflection configuration file in our project build. Told you Picocli was awesome 🙃&lt;/p&gt;

&lt;p&gt;➡️ Let's add Picocli annotation processor to our &lt;code&gt;build.gradle&lt;/code&gt;, inside the &lt;code&gt;dependencies&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;annotationProcessor &lt;span class="s1"&gt;'info.picocli:picocli-codegen:4.0.3'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;➡️ We can now repackage our app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;./gradlew assemble

...
Note: ReflectConfigGen writing to: CLASS_OUTPUT/META-INF/native-image/picocli-generated/reflect-config.json
Note: ResourceConfigGen writing to: CLASS_OUTPUT/META-INF/native-image/picocli-generated/resource-config.json
Note: ProxyConfigGen writing to: CLASS_OUTPUT/META-INF/native-image/picocli-generated/proxy-config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phew, Picocli just generated the &lt;code&gt;reflection-config.json&lt;/code&gt; file for us ! What it did is basically just adding Picocli commands and subcommands as needed classes for native compilation.&lt;/p&gt;

&lt;p&gt;Well, are we ready to compile our app down to a native image ? Well, actually we still have two problems.&lt;/p&gt;

&lt;p&gt;The first resides in the fact that dependency injection will only be added in the reflection config when using constructor injection or public fields. In other words, our private injected &lt;code&gt;WeatherAPIClient&lt;/code&gt; in &lt;code&gt;CurrentWeatherSubcommand&lt;/code&gt; and &lt;code&gt;ForecastSubcommand&lt;/code&gt; won't work while doing native compilation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Inject&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;WeatherAPIClient&lt;/span&gt; &lt;span class="n"&gt;weatherAPIClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have two choices to fix this: either make this field public or use constructor injection. Although I would always recommend the constructor injection, Picocli subcommands require a no-args constructor so we can't do that. &lt;/p&gt;

&lt;p&gt;➡️ For simplicy, let's do it the ugly way (remember to do this in both subcommands!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Inject&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;WeatherAPIClient&lt;/span&gt; &lt;span class="n"&gt;weatherAPIClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ the actual good way of doing this would be to use a &lt;a href="https://picocli.info/#_custom_factory" rel="noopener noreferrer"&gt;custom factory&lt;/a&gt; to be able to call an arg-constructor, but I'd rather keep the code simple here.&lt;/p&gt;

&lt;p&gt;Our last problem resides in our mapping API beans whose constructor and methods won't be found by Jackson after native compilation, as in the example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Type &lt;span class="o"&gt;[&lt;/span&gt;weather.cli.api.WeatherAPIClient&lt;span class="nv"&gt;$Intercepted&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; executed with error: Error decoding HTTP response body: Error decoding stream &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;class weather.cli.api.WeatherResponse]: Cannot construct instance of &lt;span class="sb"&gt;`&lt;/span&gt;weather.cli.api.WeatherResponse&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;no Creators, like default construct, exist&lt;span class="o"&gt;)&lt;/span&gt;: cannot deserialize from Object value &lt;span class="o"&gt;(&lt;/span&gt;no delegate- or property-based Creator&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;➡️ To solve this problem all we need to do is to &lt;a href="https://docs.micronaut.io/latest/guide/index.html#_understanding_micronaut_and_graal" rel="noopener noreferrer"&gt;annotate our four POJO with &lt;code&gt;@Introspected&lt;/code&gt; annotation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Introspected&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ForecastResponse&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;➡️ Repeat the operation for the four beans: &lt;code&gt;ForecastObservation&lt;/code&gt;, &lt;code&gt;ForecastResponse&lt;/code&gt;, &lt;code&gt;WeatherObservation&lt;/code&gt;, &lt;code&gt;WeatherResponse&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Phew, our configuration is now ready, we can launch native compilation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native compilation
&lt;/h3&gt;

&lt;p&gt;Native compilation is done in two steps:&lt;/p&gt;

&lt;p&gt;➡️ First build the uber-jar as usual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;./gradlew assemble
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;➡️ Now launch native compilation using &lt;a href="https://www.graalvm.org/docs/reference-manual/native-image/" rel="noopener noreferrer"&gt;GraalVM native-image tool&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; native-image &lt;span class="nt"&gt;--no-server&lt;/span&gt; &lt;span class="nt"&gt;--class-path&lt;/span&gt; build/libs/weather-cli-0.1-all.jar                                           
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]    classlist:   6,031.94 ms,  1.52 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]        &lt;span class="o"&gt;(&lt;/span&gt;cap&lt;span class="o"&gt;)&lt;/span&gt;:   2,442.65 ms,  1.52 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]        setup:   4,441.13 ms,  1.52 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]   &lt;span class="o"&gt;(&lt;/span&gt;typeflow&lt;span class="o"&gt;)&lt;/span&gt;:  67,023.02 ms, 10.51 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]    &lt;span class="o"&gt;(&lt;/span&gt;objects&lt;span class="o"&gt;)&lt;/span&gt;:  47,327.83 ms, 10.51 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]   &lt;span class="o"&gt;(&lt;/span&gt;features&lt;span class="o"&gt;)&lt;/span&gt;:   4,982.65 ms, 10.51 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]     analysis: 123,953.65 ms, 10.51 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]     &lt;span class="o"&gt;(&lt;/span&gt;clinit&lt;span class="o"&gt;)&lt;/span&gt;:   1,745.52 ms, 10.71 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]     universe:   5,704.32 ms, 10.71 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]      &lt;span class="o"&gt;(&lt;/span&gt;parse&lt;span class="o"&gt;)&lt;/span&gt;:  11,405.65 ms, 10.71 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]     &lt;span class="o"&gt;(&lt;/span&gt;inline&lt;span class="o"&gt;)&lt;/span&gt;:  19,854.90 ms, 11.61 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]    &lt;span class="o"&gt;(&lt;/span&gt;compile&lt;span class="o"&gt;)&lt;/span&gt;: 102,471.28 ms, 11.61 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]      compile: 137,496.58 ms, 11.61 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]        image:   9,296.04 ms, 11.61 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]        write:   3,119.01 ms, 11.61 GB
&lt;span class="o"&gt;[&lt;/span&gt;weather-cli:23973]      &lt;span class="o"&gt;[&lt;/span&gt;total]: 290,640.34 ms, 11.61 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see native compilation takes quite a while to complete (almost 5 minutes on my machine), but if everything goes well you should see an output similar to the one above !&lt;/p&gt;

&lt;p&gt;A few notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We run the command with &lt;code&gt;--no-server&lt;/code&gt; because we don't need server-based image building&lt;/li&gt;
&lt;li&gt;We pass our built jar as a build input using &lt;code&gt;--class-path&lt;/code&gt; argument&lt;/li&gt;
&lt;li&gt;We could have used Docker to build our native image instead of the &lt;code&gt;native-image&lt;/code&gt; tool (Micronaut provides a &lt;code&gt;docker-build.sh&lt;/code&gt; script) but I wanted to show the "hard-way" so you know what is actually going on 😉&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And now, the best part of this article: time to run the binary app !&lt;/p&gt;

&lt;p&gt;First let's verify that we have a binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lah&lt;/span&gt;

...
.rwxr-xr-x   50M olivier 23 Mar 18:38  weather-cli&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, this 50MB file is our executable, standalone binary application, that needs no other dependency to be run (SubstrateVM is already included in this binary). Pretty cool isn't it ?&lt;/p&gt;

&lt;p&gt;➡️ Time to run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run the app and ask for Montreal weather&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./weather-cli &lt;span class="nt"&gt;--country&lt;/span&gt; CA &lt;span class="nt"&gt;--city&lt;/span&gt; montreal

Established active environments: &lt;span class="o"&gt;[&lt;/span&gt;cli]
Asking weather &lt;span class="k"&gt;for &lt;/span&gt;city montreal and country ca
&lt;span class="o"&gt;=====================================&lt;/span&gt;
Current weather &lt;span class="k"&gt;in &lt;/span&gt;montreal:
  - temperature: 1.9°C
  - wind speed: 11.268 km/h
  - cloud coverage: 100.0%
  - precipitation: 0.0 mm/h
&lt;span class="o"&gt;=====================================&lt;/span&gt;

&lt;span class="c"&gt;# Ask for Paris 3-days forecast&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /weather-cli forecast &lt;span class="nt"&gt;--city&lt;/span&gt; Paris &lt;span class="nt"&gt;--days&lt;/span&gt; 3

Established active environments: &lt;span class="o"&gt;[&lt;/span&gt;cli]
Asking forecast &lt;span class="k"&gt;for &lt;/span&gt;city paris and country fr
Using default city paris...
Using default country fr...
&lt;span class="o"&gt;=====================================&lt;/span&gt;
Forecast &lt;span class="k"&gt;in &lt;/span&gt;paris on day 2020-03-23:
  - average temperature: 7.8°C
  - min temperature: 4.2°C
  - max temperature: 10.8°C
  - wind speed: 12.081204 km/h
  - probability of precipitation: 0.0%
  - average cloud coverage: 0.0%
Forecast &lt;span class="k"&gt;in &lt;/span&gt;paris on day 2020-03-24:
  - average temperature: 5.1°C
  - min temperature: 0.5°C
  - max temperature: 10.8°C
  - wind speed: 8.629308 km/h
  - probability of precipitation: 0.0%
  - average cloud coverage: 0.0%
Forecast &lt;span class="k"&gt;in &lt;/span&gt;paris on day 2020-03-25:
  - average temperature: 4.0°C
  - min temperature: 0.1°C
  - max temperature: 8.7°C
  - wind speed: 9.142452 km/h
  - probability of precipitation: 0.0%
  - average cloud coverage: 0.0%
&lt;span class="o"&gt;=====================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course the results are no different than previous outputs, but it's always good to see that our app is actually working when launched as a standalone binary !&lt;/p&gt;

&lt;h4&gt;
  
  
  Performance
&lt;/h4&gt;

&lt;p&gt;Time to measure execution time and compare it to our previous JVM execution !&lt;/p&gt;

&lt;p&gt;🥁🥁🥁 Drumroll please 🥁🥁🥁&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="s2"&gt;"./weather-cli --help"&lt;/span&gt;

Benchmark &lt;span class="c"&gt;#1: ./weather-cli --help&lt;/span&gt;
  Time &lt;span class="o"&gt;(&lt;/span&gt;mean ± σ&lt;span class="o"&gt;)&lt;/span&gt;:      39.5 ms ±   5.8 ms    &lt;span class="o"&gt;[&lt;/span&gt;User: 16.1 ms, System: 15.7 ms]
  Range &lt;span class="o"&gt;(&lt;/span&gt;min … max&lt;span class="o"&gt;)&lt;/span&gt;:    32.0 ms …  57.8 ms    67 runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wow, our response time just dropped from ~2 seconds down to an average 40ms. Pretty awesome right ?&lt;/p&gt;

&lt;p&gt;ℹ️ Again I used &lt;a href="https://github.com/sharkdp/hyperfine" rel="noopener noreferrer"&gt;&lt;code&gt;hyperfine&lt;/code&gt;&lt;/a&gt; here instead of &lt;code&gt;time&lt;/code&gt; to run a benchmark on the help command&lt;/p&gt;

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

&lt;p&gt;In this article we saw the differences between JVM and native compilation. We implemented a real-world use-case to see how to actually use native compilation to compile an application down to a native image.&lt;/p&gt;

&lt;p&gt;The main takeaway of this is that an application startup time and memory footprint can be dramatically improved by using native compilation instead of traditional JVM compilation, but also that it comes at a certain cost: not everything can be done the way it was when simply compiling for the JVM, and even when possible it can sometimes require an extra configuration step before an app can be run natively.&lt;/p&gt;

&lt;p&gt;And the great thing is: frameworks and libraries such as Micronaut and Picocli remove the need for handling reflection configuration manually by providing a set of tools to automatically generate appropriate reflection files !&lt;/p&gt;

&lt;h3&gt;
  
  
  ...beyond Micronaut
&lt;/h3&gt;

&lt;p&gt;Micronaut is not the only one to propose a simplified way of writing complex apps that can be compiled down to native images with GraalVM support. Here a few examples of frameworks that support or partially support GraalVM native compilation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://quarkus.io/" rel="noopener noreferrer"&gt;Quarkus&lt;/a&gt; by RedHat, to write "Supersonic Subatomic Java"&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://helidon.io/" rel="noopener noreferrer"&gt;Helidon&lt;/a&gt; by Oracle, a "collection of Java librairies for writing microservices"&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://projects.eclipse.org/projects/technology.microprofile" rel="noopener noreferrer"&gt;MicroProfile&lt;/a&gt; by Eclipse, a project "aimed at optimizing Enterprise Java for the microservices architecture"&lt;/li&gt;
&lt;li&gt;
&lt;a href=""&gt;SpringBoot&lt;/a&gt; by Pivotal, with experimental (and &lt;a href="https://blog.indrek.io/articles/running-spring-boot-apps-as-graalvm-native-images/" rel="noopener noreferrer"&gt;hopefully future&lt;/a&gt;) &lt;a href="https://github.com/spring-projects-experimental/spring-graal-native" rel="noopener noreferrer"&gt;GraalVM native images support&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I can only recommend the great Micronaut documentation that provides most of the information you need. You can basically find it in two places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://docs.micronaut.io/latest/guide/index.html" rel="noopener noreferrer"&gt;Micronaut user guide&lt;/a&gt; that covers most of the features Micronaut provides&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://micronaut.io/documentation.html" rel="noopener noreferrer"&gt;Micronaut documentation home page&lt;/a&gt; that provides links to specific guides. Very useful when you need to work on specific topics such as &lt;a href="https://micronaut-projects.github.io/micronaut-gcp/latest/guide/index.html" rel="noopener noreferrer"&gt;Micronaut with GCP&lt;/a&gt;, &lt;a href="https://micronaut-projects.github.io/micronaut-kafka/latest/guide/index.html" rel="noopener noreferrer"&gt;Kafka&lt;/a&gt; or &lt;a href="https://micronaut-projects.github.io/micronaut-views/latest/guide/index.html" rel="noopener noreferrer"&gt;Views&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>micronaut</category>
      <category>graalvm</category>
      <category>nativeimage</category>
      <category>jvm</category>
    </item>
    <item>
      <title>CLI applications with Micronaut and Picocli</title>
      <dc:creator>Olivier Revial</dc:creator>
      <pubDate>Mon, 01 Jun 2020 12:52:52 +0000</pubDate>
      <link>https://dev.to/stack-labs/cli-applications-with-micronaut-and-picocli-4mc8</link>
      <guid>https://dev.to/stack-labs/cli-applications-with-micronaut-and-picocli-4mc8</guid>
      <description>&lt;p&gt;In a &lt;a href="https://dev.to/stack-labs/a-micronaut-introduction-4d9l"&gt;previous article&lt;/a&gt; we introduced Micronaut framework with a simple hello-world application. Today we will see how we can easily &lt;strong&gt;create powerful CLI applications using Micronaut and Picocli&lt;/strong&gt;. In a future article we will see how we can compile our app down to a native binary using Micronaut and GraalVM ! &lt;/p&gt;

&lt;p&gt;A great use case for Micronaut is to use it with the wonderful &lt;a href="https://picocli.info/" rel="noopener noreferrer"&gt;Picocli&lt;/a&gt; library to build very simple yet very powerful Command Line Interface (CLI) applications. &lt;a href="https://picocli.info/" rel="noopener noreferrer"&gt;Picocli️&lt;/a&gt; ❤️ is an awesome library that allows creating rich command line applications for the JVM, and we can use Micronaut to enrich our application with auto-generated web clients, auto-configuration and such.&lt;/p&gt;

&lt;p&gt;So what exactly are we talking about here ? 🤔&lt;/p&gt;

&lt;p&gt;The exercice today will be to develop a simple &lt;a href="https://github.com/orevial/micronaut-weather-cli/tree/picocli-app" rel="noopener noreferrer"&gt;weather CLI application&lt;/a&gt; that will connect to a distant weather API to give us current weather and forecast for a given location.&lt;/p&gt;

&lt;p&gt;We will use &lt;a href="https://picocli.info/" rel="noopener noreferrer"&gt;Picocli️&lt;/a&gt; features to easily respond to CLI commands and we will use Micronaut to interact with the distant weather API, with Micronaut &lt;code&gt;http-client&lt;/code&gt; library. Of course for this simple example it could make more sense to use &lt;a href="https://openjdk.java.net/groups/net/httpclient/intro.html" rel="noopener noreferrer"&gt;Java 11 native HTTP client&lt;/a&gt; but for the sake of this exercise, let's stick with Micronaut 🚀. It will allow us to see how Micronaut works in this context (and of course it could be extended with richer features such as auto-configuration) and this simple CLI application will also be a good playground for the native compilation coming in the next article 😉 &lt;/p&gt;

&lt;p&gt;In this exercice we will use &lt;a href="https://www.weatherbit.io/features" rel="noopener noreferrer"&gt;Weatherbit.io API&lt;/a&gt; to add two commands to our CLI application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;weather&lt;/code&gt; command will return the current weather for a given location (city and country)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;forecast&lt;/code&gt; command will return a forecast for the nehugxt 1 to 16 days for a given location (city and country)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👩‍💻👨‍💻 Code sample for this article can be found in &lt;a href="https://github.com/orevial/micronaut-weather-cli/tree/graalvm-app" rel="noopener noreferrer"&gt;this Github repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate a CLI Micronaut application
&lt;/h2&gt;

&lt;p&gt;As Micronaut applications are quite simple we could create ours from scratch, but as explained in our &lt;a href="https://dev.to/stack-labs/a-micronaut-introduction-4d9l"&gt;Micronaut 101 article&lt;/a&gt; we will instead use Micronaut &lt;code&gt;mn&lt;/code&gt; command to generate our app skeleton, mostly to pre-configure our build with Picocli dependency.&lt;/p&gt;

&lt;p&gt;➡️ Run the following command to generate the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; mn create-cli-app weather-cli --features http-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ We told Micronaut to create a CLI application, thus we avoid the use of an HTTP server (useless in our case).&lt;/p&gt;

&lt;p&gt;➡️ Let's now have a look at generated build file &lt;code&gt;build.gradle&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    annotationProcessor platform("io.micronaut:micronaut-bom:$micronautVersion")
    annotationProcessor "io.micronaut.configuration:micronaut-picocli"
    annotationProcessor "io.micronaut:micronaut-inject-java"
    annotationProcessor "io.micronaut:micronaut-validation"
    implementation platform("io.micronaut:micronaut-bom:$micronautVersion")
    implementation "io.micronaut:micronaut-runtime"
    implementation "info.picocli:picocli"
    implementation "io.micronaut.configuration:micronaut-picocli"
    implementation "io.micronaut:micronaut-inject"
    implementation "io.micronaut:micronaut-validation"
    implementation "io.micronaut:micronaut-http-client"
    runtimeOnly "ch.qos.logback:logback-classic:1.2.3"
    testAnnotationProcessor platform("io.micronaut:micronaut-bom:$micronautVersion")
    testAnnotationProcessor "io.micronaut:micronaut-inject-java"
    testImplementation platform("io.micronaut:micronaut-bom:$micronautVersion")
    testImplementation "org.junit.jupiter:junit-jupiter-api"
    testImplementation "io.micronaut.test:micronaut-test-junit5"
    testImplementation "io.micronaut:micronaut-inject-java"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amongst other things we can see that Micronaut added a few dependencies, including Micronaut core dependencies (java-inject, validation, runtime, etc.) along with Picocli dependency. &lt;/p&gt;

&lt;h2&gt;
  
  
  Add Picocli commands and Micronaut HTTP client
&lt;/h2&gt;

&lt;p&gt;➡️ First let's look at &lt;code&gt;WeatherCliCommand&lt;/code&gt;, the default main class that was generated for us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"weather-cli"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;mixinStandardHelpOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherCliCommand&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Runnable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"-v"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--verbose"&lt;/span&gt;&lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;PicocliRunner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WeatherCliCommand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// business logic here&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hi!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run the application now you should see a very basic CLI application that works but doesn't do much (except printing "Hi!" if called with the &lt;code&gt;-v&lt;/code&gt; option). We will now add the &lt;code&gt;weather&lt;/code&gt; and &lt;code&gt;forecast&lt;/code&gt; subcommands to add behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with Picocli commands
&lt;/h3&gt;

&lt;p&gt;As both subcommands will need to take similar city and country inputs, let's factorize this.&lt;/p&gt;

&lt;p&gt;➡️ Create a &lt;code&gt;LocalizedCommand&lt;/code&gt; class that simply declares a &lt;code&gt;city&lt;/code&gt; and a &lt;code&gt;country&lt;/code&gt;, and validates these inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.slf4j.Logger&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.slf4j.LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;picocli.CommandLine&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;picocli.CommandLine.Option&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;picocli.CommandLine.Spec&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocalizedCommand&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Runnable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="no"&gt;LOG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ForecastSubcommand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@Spec&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nc"&gt;CommandLine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CommandSpec&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_CITY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"paris"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_COUNTRY_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"fr"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"--country"&lt;/span&gt;&lt;span class="o"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"the 2-letters country code"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_COUNTRY_CODE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;showDefaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ALWAYS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setCountry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;substring&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toLowerCase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CommandLine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ParameterException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commandLine&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="s"&gt;"Country parameter must be a 2-letters code"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"--city"&lt;/span&gt;&lt;span class="o"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"the city name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_CITY&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;showDefaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ALWAYS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setCity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toLowerCase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Asking forecast for city {} and country {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_CITY&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Using default city {}..."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_COUNTRY_CODE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Using default country {}..."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect, we now have the base parameters ready. Time to create real subcommands !&lt;/p&gt;

&lt;p&gt;➡️ First, let's create the &lt;code&gt;weather&lt;/code&gt; subcommand that simply returns the result of the Weather API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.slf4j.Logger&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.slf4j.LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;picocli.CommandLine.Command&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;weather.cli.api.WeatherAPIClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;weather.cli.api.WeatherResponse&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"weather"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"gives weather for a given location"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CurrentWeatherSubcommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LocalizedCommand&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="no"&gt;LOG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CurrentWeatherSubcommand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;WeatherAPIClient&lt;/span&gt; &lt;span class="n"&gt;weatherAPIClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;WeatherResponse&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weatherAPIClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"====================================="&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Current weather in {}: \n{}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getData&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"====================================="&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course this will not compile as we don't have the API client interface yet, nor the models. You can create the interface now if you want but we will see this interface in a minute. API models can be found in &lt;a href="https://github.com/orevial/micronaut-weather-cli/tree/picocli-app" rel="noopener noreferrer"&gt;source repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;➡️ We can now create the &lt;code&gt;forecast&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.slf4j.Logger&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.slf4j.LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;picocli.CommandLine&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;picocli.CommandLine.Command&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;picocli.CommandLine.Option&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;weather.cli.api.ForecastObservation&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;weather.cli.api.ForecastResponse&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;weather.cli.api.WeatherAPIClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.inject.Inject&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Comparator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;comparing&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;picocli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CommandLine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Help&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Visibility&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ALWAYS&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"forecast"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"gives forecast for given city"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ForecastSubcommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LocalizedCommand&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="no"&gt;LOG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ForecastSubcommand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;WeatherAPIClient&lt;/span&gt; &lt;span class="n"&gt;weatherAPIClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;nbDays&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"-d"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--days"&lt;/span&gt;&lt;span class="o"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"the number of forecast days to fetch (between 1 and 16)"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;showDefaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ALWAYS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setNbDays&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;nbDays&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nbDays&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;nbDays&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CommandLine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ParameterException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commandLine&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="s"&gt;"Forecast must be between 1 and 16 days"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nbDays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nbDays&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;ForecastResponse&lt;/span&gt; &lt;span class="n"&gt;forecast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weatherAPIClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forecast&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nbDays&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"====================================="&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;forecast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getData&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sorted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comparing&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;ForecastObservation:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getForecastDate&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forecastObservation&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Forecast in {} on day {}: \n{}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forecastObservation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getForecastDate&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;forecastObservation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"====================================="&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class is similar to the &lt;code&gt;weather&lt;/code&gt; subcommand except that it takes (and validates) an extra &lt;code&gt;--days&lt;/code&gt; parameter that corresponds to the number of days we want the forecast for, and loop on the API response to display forecast of each coming day.&lt;/p&gt;

&lt;p&gt;➡️ Great, we now have our two subcommands, let's add them to the "main" command, we can now open &lt;code&gt;WeatherCliCommand&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.configuration.picocli.PicocliRunner&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;picocli.CommandLine.Command&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;weather.cli.commands.CurrentWeatherSubcommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;weather.cli.commands.ForecastSubcommand&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subcommands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;CurrentWeatherSubcommand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;ForecastSubcommand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;
&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherCliCommand&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Runnable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;PicocliRunner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WeatherCliCommand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Welcome to weather app..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Working with Micronaut HTTP Client
&lt;/h3&gt;

&lt;p&gt;Creating an HTTP client with Micronaut is dead-simple. Before creating it, let's look at &lt;a href="https://www.weatherbit.io/features" rel="noopener noreferrer"&gt;Weatherbit API endpoints&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.weatherbit.io/api/weather-current" rel="noopener noreferrer"&gt;Current weather endpoint&lt;/a&gt;:

&lt;ul&gt;
&lt;li&gt;Requires an API key&lt;/li&gt;
&lt;li&gt;Takes a &lt;code&gt;city&lt;/code&gt; and a &lt;code&gt;country&lt;/code&gt; to localize the request&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://www.weatherbit.io/api/weather-forecast-16-day" rel="noopener noreferrer"&gt;16 days forecast endpoint&lt;/a&gt;:

&lt;ul&gt;
&lt;li&gt;Requires an API key&lt;/li&gt;
&lt;li&gt;Takes a &lt;code&gt;city&lt;/code&gt; and a &lt;code&gt;country&lt;/code&gt; to localize the request&lt;/li&gt;
&lt;li&gt;Takes an optional &lt;code&gt;days&lt;/code&gt; parameter to specify the number of days to include in the forecast (defaults to 16)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;➡️ Alright, time to create the &lt;code&gt;WeatherAPIClient&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.http.annotation.Get&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.http.annotation.QueryValue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.http.client.annotation.Client&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Client&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${weather.api.url}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;WeatherAPIClient&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/current?key=${weather.api.key}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;WeatherResponse&lt;/span&gt; &lt;span class="nf"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@QueryValue&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                   &lt;span class="nd"&gt;@QueryValue&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@Get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/forecast/daily?key=${weather.api.key}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;ForecastResponse&lt;/span&gt; &lt;span class="nf"&gt;forecast&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@QueryValue&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="nd"&gt;@QueryValue&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="nd"&gt;@QueryValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What can we see here ?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We just need an interface. No need to write a client implementation, Micronaut will inject an implementation for us when needed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Client&lt;/code&gt; annotation declares a Micronaut HTTP client, and we pass it a configuration key named &lt;code&gt;weather.api.url&lt;/code&gt; as the server base URL. Micronaut will resolve this configuration at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Get&lt;/code&gt; annotation declares a HTTP Get endpoint and we tell it to add a query parameter from the configuration key &lt;code&gt;weather.api.key&lt;/code&gt;. We pass it by configuration to avoid putting our API key directly in the code (so you don't steal my API key 🔒👮)&lt;/li&gt;
&lt;li&gt;Both methods declare &lt;code&gt;@QueryValue&lt;/code&gt; parameters that will be added to the URL by Micronaut at runtime.&lt;/li&gt;
&lt;li&gt;Both methods returns a simple POJO response that will be automatically converted from the API json response by Micronaut with the help of &lt;a href="https://docs.micronaut.io/latest/guide/index.html#_reading_json_responses" rel="noopener noreferrer"&gt;Jackson ObjectMapper&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As said above, we added a query parameter named &lt;code&gt;weather.api.key&lt;/code&gt; that we want to be filled in our configuration.&lt;/p&gt;

&lt;p&gt;➡️ Let's open the &lt;code&gt;application.yml&lt;/code&gt; file in &lt;code&gt;main/resources&lt;/code&gt; and modify it so it matches the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;micronaut&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;application&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;weather-cli&lt;/span&gt;

&lt;span class="na"&gt;weather&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.weatherbit.io/v2.0/&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;invalid-api-key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But right now you might think that I'm crazy to put my configuration key in a configuration file that is going to be versioned in my public Github repository... well, I won't put my real key. To avoid this we could tell Micronaut that this key uses an environment variable by defining our property as such :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;weather.api.key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${WEATHER_API_KEY:invalid-api-key}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact we don't need this extra definition as Micronaut uses "&lt;a href="https://docs.micronaut.io/latest/guide/index.html#_property_value_binding" rel="noopener noreferrer"&gt;Property Value Binding&lt;/a&gt;", which means that &lt;code&gt;WEATHER_API_KEY&lt;/code&gt; environment variable value will automatically be assigned and override our property key &lt;code&gt;weather.api.key&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Running our application
&lt;/h2&gt;

&lt;p&gt;➡️ Alright, time to run our application and generate a uber-jar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ./gradlew assemble
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;➡️ We can now run it and ask for current weather in Montreal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# First export WEATHER_API_KEY (replace with your own key)
&amp;gt; export WEATHER_API_KEY=WEATHER-BIT-IO-GENERATED-API-KEY

# Run the app and ask for Montreal weather
&amp;gt; java -jar build/libs/weather-cli-0.1-all.jar weather --country CA --city montreal

Established active environments: [cli]
Asking weather for city montreal and country ca
=====================================
Current weather in montreal:
  - temperature: 8.3°C
  - wind speed: 3.6 km/h
  - cloud coverage: 100.0%
  - precipitation: 6.0 mm/h
=====================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;➡️ Let's now ask for Paris 3-days forecast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Ask for Paris 3-days forecast
&amp;gt; java -jar build/libs/weather-cli-0.1-all.jar forecast --city Paris --days 3

Established active environments: [cli]
Asking forecast for city paris and country fr
Using default city paris...
Using default country fr...
=====================================
Forecast in paris on day 2020-04-30:
  - average temperature: 13.4°C
  - min temperature: 11.6°C
  - max temperature: 14.9°C
  - wind speed: 17.962884000000003 km/h
  - probability of precipitation: 75.0%
  - average cloud coverage: 86.0%
Forecast in paris on day 2020-05-01:
  - average temperature: 13.1°C
  - min temperature: 10.0°C
  - max temperature: 17.7°C
  - wind speed: 13.19382 km/h
  - probability of precipitation: 75.0%
  - average cloud coverage: 74.0%
Forecast in paris on day 2020-05-02:
  - average temperature: 12.5°C
  - min temperature: 9.1°C
  - max temperature: 16.9°C
  - wind speed: 10.229436 km/h
  - probability of precipitation: 0.0%
  - average cloud coverage: 60.0%
=====================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, everything works as expected 😊&lt;/p&gt;

&lt;p&gt;👏 Congrats, you have now successfully created a CLI application using Micronaut and Picocli !  &lt;/p&gt;

&lt;h2&gt;
  
  
  A note on performance...
&lt;/h2&gt;

&lt;p&gt;Let's time the execution time of the help command (we don't time our subcommands because they use network time to talk to the API and this would make any comparison flawed) to have a better idea of the startup time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; time "java -jar build/libs/weather-cli-0.1-all.jar --help"

Benchmark #1: java -jar build/libs/weather-cli-0.1-all.jar --help
  Time (mean ± σ):      2.279 s ±  0.172 s    [User: 2.764 s, System: 0.216 s]
  Range (min … max):    2.075 s …  2.732 s    10 runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ I used &lt;a href="https://github.com/sharkdp/hyperfine" rel="noopener noreferrer"&gt;&lt;code&gt;hyperfine&lt;/code&gt;&lt;/a&gt; here instead of &lt;code&gt;time&lt;/code&gt; to run a benchmark on the help command&lt;/p&gt;

&lt;p&gt;We have a mean answer time of approximately 2 seconds. Although it can be satisfying for a JVM app, it's not great for a CLI application.  &lt;/p&gt;

&lt;p&gt;So how do we improve that ?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frlowvamgytnb5pqthgih.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frlowvamgytnb5pqthgih.jpg" alt="spoiler alert" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The answer: &lt;a href="https://dev.to/stack-labs/native-images-with-micronaut-and-graalvm-3cn8"&gt;&lt;strong&gt;GraalVM native compilation&lt;/strong&gt;&lt;/a&gt; !&lt;/p&gt;

</description>
      <category>micronaut</category>
      <category>java</category>
      <category>picocli</category>
      <category>microservices</category>
    </item>
    <item>
      <title>A Micronaut introduction</title>
      <dc:creator>Olivier Revial</dc:creator>
      <pubDate>Mon, 01 Jun 2020 08:09:12 +0000</pubDate>
      <link>https://dev.to/stack-labs/a-micronaut-introduction-4d9l</link>
      <guid>https://dev.to/stack-labs/a-micronaut-introduction-4d9l</guid>
      <description>&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: this article was originally published on &lt;a href="https://blog.stack-labs.com/code/introduction_to_micronaut/" rel="noopener noreferrer"&gt;Stack Labs blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until not too long ago, when we wanted to develop JVM microservices, there wasn't much choice beyond the almighty Spring Boot. This is not true anymore with the arrival of frameworks such as Eclipse Microprofile, Helidon, Quarkus or Micronaut. In this article we will focus on &lt;strong&gt;Micronaut&lt;/strong&gt; framework to discover how we can use it to develop microservices through a simple hello-world app example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Micronaut goal
&lt;/h2&gt;

&lt;p&gt;You might be wondering why using another JVM framework when Spring Boot is already working quite well for us.   &lt;/p&gt;

&lt;p&gt;If we read &lt;a href="https://docs.micronaut.io/latest/guide/index.html" rel="noopener noreferrer"&gt;Micronaut official documentation introduction&lt;/a&gt;, it becomes clear what Micronaut claims to provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast startup time&lt;/li&gt;
&lt;li&gt;Reduced memory footprint&lt;/li&gt;
&lt;li&gt;Minimal use of reflection&lt;/li&gt;
&lt;li&gt;Minimal use of proxies&lt;/li&gt;
&lt;li&gt;Easy unit testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By providing these points Micronaut clearly takes a completely different approach to the one taken by older frameworks like Spring Boot, Spring or Grails, and Micronaut developers know what they are talking about because they are...  former Grails framework developers !&lt;/p&gt;

&lt;p&gt;Although there are downsides to famous historical frameworks, a lot of things work well (well, that's why frameworks like Spring Boot became so popular, isn't it ?) and Micronaut intends to keep all these well-working things, such as dependency injection, auto-configuration, service discovery and HTTP clients and servers.&lt;/p&gt;

&lt;p&gt;Now that we know what Micronaut goal is, let's dive into a real Micronaut application to see concretely how it goes for us developers ⌨️️&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a fully-featured app skeleton with Micronaut CLI
&lt;/h2&gt;

&lt;p&gt;All the great frameworks provide a quick way to create a pre-configured app skeleton to enable for a very quick start. The idea behind this being that you spend less time initializing and configuring your app and more time actually coding and adding business value.     &lt;/p&gt;

&lt;p&gt;If you've ever seen one of &lt;a href="https://youtu.be/4-vEW8Ck_6g?t=1358" rel="noopener noreferrer"&gt;Pivotal best speaker Josh Long&lt;/a&gt;, you already know "&lt;a href="http://start.spring.io/" rel="noopener noreferrer"&gt;start dot spring dot io&lt;/a&gt;" where you can select the various features and other configurations for your brand new app.  &lt;/p&gt;

&lt;p&gt;Well Micronaut does not provide a web UI but a simple CLI called &lt;code&gt;mn&lt;/code&gt; to allow easy app creation. You can easily &lt;a href="https://docs.micronaut.io/latest/guide/index.html#buildCLI" rel="noopener noreferrer"&gt;install it&lt;/a&gt; using Sdkman or directly with Homebrew on a Mac.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mac install&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; brew update &amp;amp;&amp;amp; brew install micronaut
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Unix install&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# With Sdkman installed
&amp;gt; sdk update &amp;amp;&amp;amp; sdk install micronaut
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;➡️ Let's see what features Micronaut provides by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; mn profile-info service

Resolving dependencies..
| Profile: service
--------------------
The service profile

| Provided Commands:
--------------------
  create-bean              Creates a singleton bean
  create-client            Creates a client interface
  create-controller        Creates a controller and associated test
  create-job               Creates a job with scheduled method
  create-repository        Creates a repository and associated test
  create-test              Creates a simple test for the project's testing framework
  create-websocket-client  Creates a Websocket client
  create-websocket-server  Creates a Websocket server
  help                     Prints help information for a specific command

| Provided Features:
| (+) denotes features included by default.
--------------------
  annotation-api (+)        Adds Java annotation API
  application (+)           Facilitates creating an executable JVM application and adds support for creating fat/uber JARs
  asciidoctor               Adds Asciidoctor documentation
  aws-api-gateway           Adds support for AWS API Gateway
  aws-api-gateway-graal     Creates an AWS API Gateway Proxy Lambda with Graal Native Image
  cassandra                 Adds support for Cassandra in the application
  config-consul             Adds support for Distributed Configuration with Consul (https://www.consul.io)
  data-hibernate-jpa        Adds support for Micronaut Data Hibernate/JPA
  data-jdbc                 Adds support for Micronaut Data JDBC
  discovery-consul          Adds support for Service Discovery with Consul (https://www.consul.io)
  discovery-eureka          Adds support for Service Discovery with Eureka
  ehcache                   Adds support for Ehcache (https://www.ehcache.org/)
  elasticsearch             Adds support for Elasticsearch in the application
  file-watch                Adds automatic restarts and file watch
  flyway                    Adds support for Flyway database migrations (https://flywaydb.org/)
  graal-native-image        Allows Building a GraalVM Native Image
  graphql                   Adds support for GraphQL in the application
  groovy                    Creates a Groovy application
  hazelcast                 Adds support for Hazelcast (https://hazelcast.org)
  hibernate-gorm            Adds support for GORM persistence framework
  hibernate-jpa             Adds support for Hibernate/JPA
  http-client (+)           Adds support for creating HTTP clients
  http-server (+)           Adds support for running a Netty server
  java                      Creates a Java application
  jdbc-dbcp                 Configures SQL DataSource instances using Commons DBCP
  jdbc-hikari               Configures SQL DataSource instances using Hikari Connection Pool
  jdbc-tomcat               Configures SQL DataSource instances using Tomcat Connection Pool
  jib                       Adds support for Jib builds
  jrebel                    Adds support for class reloading with JRebel (requires separate JRebel installation)
  junit                     Adds support for the JUnit 5 testing framework
  kafka                     Adds support for Kafka
  kafka-streams             Adds support for Kafka Streams
  kotlin                    Creates a Kotlin application
  kotlintest                Adds support for the KotlinTest testing framework
  kubernetes                Adds support for Kubernetes
  liquibase                 Adds support for Liquibase database migrations (http://www.liquibase.org/)
  log4j2                    Adds Log4j2 Logging
  logback (+)               Adds Logback Logging
  management                Adds support for management endpoints
  micrometer                Adds support for Micrometer metrics
  micrometer-appoptics      Adds support for Micrometer metrics (w/ AppOptics reporter)
  micrometer-atlas          Adds support for Micrometer metrics (w/ Atlas reporter)
  micrometer-azure-monitor  Adds support for Micrometer metrics (w/ Azure Monitor reporter)
  micrometer-cloudwatch     Adds support for Micrometer metrics (w/ AWS Cloudwatch reporter)
  micrometer-datadog        Adds support for Micrometer metrics (w/ Datadog reporter)
  micrometer-dynatrace      Adds support for Micrometer metrics (w/ Dynatrace reporter)
  micrometer-elastic        Adds support for Micrometer metrics (w/ Elastic reporter)
  micrometer-ganglia        Adds support for Micrometer metrics (w/ Ganglia reporter)
  micrometer-graphite       Adds support for Micrometer metrics (w/ Graphite reporter)
  micrometer-humio          Adds support for Micrometer metrics (w/ Humio reporter)
  micrometer-influx         Adds support for Micrometer metrics (w/ Influx reporter)
  micrometer-jmx            Adds support for Micrometer metrics (w/ Jmx reporter)
  micrometer-kairos         Adds support for Micrometer metrics (w/ Kairos reporter)
  micrometer-new-relic      Adds support for Micrometer metrics (w/ New Relic reporter)
  micrometer-prometheus     Adds support for Micrometer metrics (w/ Prometheus reporter)
  micrometer-signalfx       Adds support for Micrometer metrics (w/ SignalFx reporter)
  micrometer-stackdriver    Adds support for Micrometer metrics (w/ Stackdriver reporter)
  micrometer-statsd         Adds support for Micrometer metrics (w/ Statsd reporter)
  micrometer-wavefront      Adds support for Micrometer metrics (w/ Wavefront reporter)
  mongo-gorm                Configures GORM for MongoDB for Groovy applications
  mongo-reactive            Adds support for the Mongo Reactive Streams Driver
  neo4j-bolt                Adds support for the Neo4j Bolt Driver
  neo4j-gorm                Configures GORM for Neo4j for Groovy applications
  netflix-archaius          Adds support for Netflix Archaius in the application
  netflix-hystrix           Adds support for Netflix Hystrix in the application
  netflix-ribbon            Adds support for Netflix Ribbon in the application
  picocli                   Adds support for command line parsing (http://picocli.info)
  postgres-reactive         Adds support for the Reactive Postgres driver in the application
  rabbitmq                  Adds support for RabbitMQ in the application
  redis-lettuce             Configures the Lettuce driver for Redis
  security-jwt              Adds support for JWT (JSON Web Token) based Authentication
  security-session          Adds support for Session based Authentication
  spek                      Adds support for the Spek testing framework
  spock                     Adds support for the Spock testing framework
  springloaded              Adds support for class reloading with Spring-Loaded
  swagger-groovy            Configures Swagger (OpenAPI) Integration for Groovy
  swagger-java              Configures Swagger (OpenAPI) Integration for Java
  swagger-kotlin            Configures Swagger (OpenAPI) Integration for Kotlin
  tracing-jaeger            Adds support for distributed tracing with Jaeger (https://www.jaegertracing.io)
  tracing-zipkin            Adds support for distributed tracing with Zipkin (https://zipkin.io)
  vertx-mysql-client        Add support for the Reactive MySQL Client in the application
  vertx-pg-client           Add support for the Reactive Postgres Client in the application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, we can see all the features provided by Micronaut. By the way we can note that some features such as &lt;code&gt;annotation-api&lt;/code&gt;, &lt;code&gt;application&lt;/code&gt;, &lt;code&gt;http-client&lt;/code&gt;, &lt;code&gt;http-server&lt;/code&gt; or &lt;code&gt;logback&lt;/code&gt; are included by default in any application created through &lt;code&gt;mn&lt;/code&gt; command as they are considered basic features for any real-world application 🙂 &lt;/p&gt;

&lt;p&gt;➡️ Let's now create a &lt;code&gt;hello-world&lt;/code&gt; Java application with a few features (plus features included by default) and with Maven build tool (&lt;code&gt;mn&lt;/code&gt; defaults to Gradle):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; mn create-app com.stacklabs.micronaut.hello-world --lang=java --build=maven --features=junit 
| Generating Java project...
| Application created at /Users/path/to/the/micronaut-workshop/hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, we now have our app skeleton, time to code !&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Micronaut CLI is great for initializing an app and avoiding the creation of Maven/Gradle boilerplate, but it is pretty basic and you cannot use it to add new features to your app once it has been initialized. If you have forgotten features, you will have to add them manually on your existing app build. &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a simple controller
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The controller
&lt;/h3&gt;

&lt;p&gt;We will now create a controller, but before that take a look at the main class generated for us by &lt;code&gt;mn&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Micronaut&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not too complicated right ?&lt;/p&gt;

&lt;p&gt;➡️ Now, to create our controller we can use &lt;code&gt;mn&lt;/code&gt; command again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; mn create-controller Hello
| Rendered template Controller.java to destination src/main/java/fr/stacklabs/HelloController.java
| Rendered template Spec.groovy to destination src/test/groovy/fr/stacklabs/HelloControllerSpec.groovy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, the command line generated a Java class (because we created a Java app) for our controller and its test counterpart. Let's have a look at this controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.http.annotation.Controller&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.http.annotation.Get&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.http.HttpStatus&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Controller&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;HttpStatus&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty basic, the default skeleton creates a controller with a simple endpoint returning a &lt;code&gt;HTTP 200&lt;/code&gt;. Note that the &lt;code&gt;@Controller&lt;/code&gt; and  &lt;code&gt;@Get&lt;/code&gt;  annotations are not JAX-RS annotations but Micronaut specific annotations. &lt;/p&gt;

&lt;p&gt;➡️ Let's modify it so it adds "Hello Micronaut !" to our HTTP 200 response body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Controller&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello Micronaut !"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The test
&lt;/h3&gt;

&lt;p&gt;➡️ Let's now open the test and check the default content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.context.ApplicationContext&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.http.HttpStatus&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.http.client.RxHttpClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.runtime.server.EmbeddedServer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.micronaut.test.annotation.MicronautTest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.inject.Inject&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jupiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Assertions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@MicronautTest&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloControllerTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nc"&gt;EmbeddedServer&lt;/span&gt; &lt;span class="n"&gt;embeddedServer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testIndex&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RxHttpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddedServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getApplicationContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;createBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RxHttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddedServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getURL&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toBlocking&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What do we do have here ?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;@MicronautTest annotation tells Micronaut that it needs to make the ApplicationContext available for our test&lt;/li&gt;
&lt;li&gt;We use a constructor injection to inject an embedded server into our test&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/test"&gt;@test&lt;/a&gt; is a simple JUnit 5 annotation, nothing new here&lt;/li&gt;
&lt;li&gt;Using the application context of the embedded server, we will create a simple HTTP Client bean and we configure it to connect to our embedded server (dynamic) url&lt;/li&gt;
&lt;li&gt;We then simply execute a blocking GET request to test our very first controller method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;➡️ Although this test will work (we are returning a string within an HTTP 200), let's modify it to test our "hello" string response instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@MicronautTest&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloControllerTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nc"&gt;EmbeddedServer&lt;/span&gt; &lt;span class="n"&gt;embeddedServer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testIndex&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RxHttpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddedServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getApplicationContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;createBean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RxHttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddedServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getURL&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello Micronaut !"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;retrieve&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;blockingFirst&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are now ready to run the test using maven wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ./mvnw test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once finished, you should see an output similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.stacklabs.micronaut.workshop.agency.HelloControllerTest
23:18:45.538 [main] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [test]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.62 s - in com.stacklabs.micronaut.workshop.agency.HelloControllerTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the app
&lt;/h3&gt;

&lt;p&gt;You probably love tests but you might want to run the app, so let's do that.&lt;/p&gt;

&lt;p&gt;➡️ First let's compile and package our application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ./mvnw clean install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default Micronaut will add Shadow Jar dependencies and plugins to our build so it generates two jars, the "original" one that only contain &lt;br&gt;
our application classes, and a self-contained executable "uber-jar" (or fat-jar) that we can deploy as is.  &lt;/p&gt;

&lt;p&gt;➡️ Let's check the output of our build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ls -l target/
-rw-rw-r-- 1 user group  13M mars  10 13:14 hello-world-0.1.jar
-rw-rw-r-- 1 user group  11K mars  10 13:14 original-hello-world-0.1.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;➡️ We can now launch our app using the fat-jar and interact with it using our browser or curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; java -jar target/hello-world-0.1.jar
13:24:15.361 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 968ms. Server Running: http://localhost:8080
&amp;gt; curl http://localhost:8080/hello
Hello Micronaut !
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, we now have a working REST application that we can test, compile and run !&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further...
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ...with Micronaut
&lt;/h3&gt;

&lt;p&gt;Of course we only created and developed a simple hello-world application so you might be a bit frustrated, but the goal of this article was simply showing how simple it is to both create and develop a Micronaut application. &lt;/p&gt;

&lt;p&gt;You may ask yourself: where should I start to dive further into Micronaut framework ? Well it all depends on what you are trying to achieve but here are some hints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.micronaut.io/latest/guide/index.html#graal" rel="noopener noreferrer"&gt;Native compilation with GraalVM&lt;/a&gt;, to generate native binaries that run in a few milliseconds.&lt;/li&gt;
&lt;li&gt;Complete &lt;a href="https://docs.micronaut.io/latest/guide/index.html#httpServer" rel="noopener noreferrer"&gt;REST servers&lt;/a&gt; with URL and body params, &lt;a href="https://docs.micronaut.io/latest/guide/index.html#reactiveResponses" rel="noopener noreferrer"&gt;reactive APIs&lt;/a&gt; and &lt;a href="https://docs.micronaut.io/latest/guide/index.html#httpClient" rel="noopener noreferrer"&gt;HTTP clients&lt;/a&gt; to both test your servers and interact with external services such as remote HTTP APIs.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.micronaut.io/latest/guide/index.html#config" rel="noopener noreferrer"&gt;Configuration and auto-configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Data integration with &lt;a href="https://micronaut-projects.github.io/micronaut-data/latest/guide/index.html" rel="noopener noreferrer"&gt;Micronaut Data&lt;/a&gt; for relational databases or support for various data provider such as &lt;a href="https://micronaut-projects.github.io/micronaut-mongodb/latest/guide/index.html" rel="noopener noreferrer"&gt;MongoDB&lt;/a&gt;, &lt;a href="https://micronaut-projects.github.io/micronaut-redis/latest/guide/index.html" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; or &lt;a href="https://micronaut-projects.github.io/micronaut-cassandra/latest/guide/index.html" rel="noopener noreferrer"&gt;Cassandra&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Microservices observability with &lt;a href="https://docs.micronaut.io/latest/guide/index.html#management" rel="noopener noreferrer"&gt;Management &amp;amp; Monitoring&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.micronaut.io/latest/guide/index.html#cloud" rel="noopener noreferrer"&gt;Cloud native features&lt;/a&gt; with support for various cloud through auto-configuration and such, &lt;a href="https://docs.micronaut.io/latest/guide/index.html#serviceDiscovery" rel="noopener noreferrer"&gt;service discovery&lt;/a&gt; or &lt;a href="https://docs.micronaut.io/latest/guide/index.html#serverlessFunctions" rel="noopener noreferrer"&gt;serverless functions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Standalone CLI applications with the great &lt;a href="https://micronaut-projects.github.io/micronaut-picocli/latest/guide/" rel="noopener noreferrer"&gt;Picocli ♥️&lt;/a&gt; parser integration !&lt;/li&gt;
&lt;li&gt;... and so much more ! &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ...beyond Micronaut
&lt;/h3&gt;

&lt;p&gt;As stated in the introduction we now have quite a lot of new JVM frameworks that aim to answer today's microservices needs. Although we won't compare them to Micronaut in this article (maybe in a future article ?), I'll still leave some links here in case you want to dig into one of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://quarkus.io/" rel="noopener noreferrer"&gt;Quarkus&lt;/a&gt; by RedHat, to write "Supersonic Subatomic Java"&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://helidon.io/" rel="noopener noreferrer"&gt;Helidon&lt;/a&gt; by Oracle, a "collection of Java librairies for writing microservices"&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://projects.eclipse.org/projects/technology.microprofile" rel="noopener noreferrer"&gt;MicroProfile&lt;/a&gt; by Eclipse, a project "aimed at optimizing Enterprise Java for the microservices architecture"&lt;/li&gt;
&lt;li&gt;
&lt;a href=""&gt;SpringBoot&lt;/a&gt; by Pivotal, with experimental (and &lt;a href="https://blog.indrek.io/articles/running-spring-boot-apps-as-graalvm-native-images/" rel="noopener noreferrer"&gt;hopefully future&lt;/a&gt;) &lt;a href="https://github.com/spring-projects-experimental/spring-graal-native" rel="noopener noreferrer"&gt;GraalVM native images support&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I can only recommend the great Micronaut documentation that provides most of the information you need. You can basically find it in two places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://docs.micronaut.io/latest/guide/index.html" rel="noopener noreferrer"&gt;Micronaut user guide&lt;/a&gt; that covers most of the features Micronaut provides&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://micronaut.io/documentation.html" rel="noopener noreferrer"&gt;Micronaut documentation home page&lt;/a&gt; that provides links to specific guides. Very useful when you need to work on specific topics such as &lt;a href="https://micronaut-projects.github.io/micronaut-gcp/latest/guide/index.html" rel="noopener noreferrer"&gt;Micronaut with GCP&lt;/a&gt;, &lt;a href="https://micronaut-projects.github.io/micronaut-kafka/latest/guide/index.html" rel="noopener noreferrer"&gt;Kafka&lt;/a&gt; or &lt;a href="https://micronaut-projects.github.io/micronaut-views/latest/guide/index.html" rel="noopener noreferrer"&gt;Views&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>micronaut</category>
      <category>java</category>
      <category>kotlin</category>
      <category>microservices</category>
    </item>
  </channel>
</rss>
