<?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: Bram De Coninck</title>
    <description>The latest articles on DEV Community by Bram De Coninck (@bramdc3).</description>
    <link>https://dev.to/bramdc3</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%2F309809%2F519d5bfe-442b-4b8c-8483-89b130c66f1b.jpeg</url>
      <title>DEV Community: Bram De Coninck</title>
      <link>https://dev.to/bramdc3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bramdc3"/>
    <language>en</language>
    <item>
      <title>Flutter Europe 2020: My First Developer Conference</title>
      <dc:creator>Bram De Coninck</dc:creator>
      <pubDate>Sat, 08 Feb 2020 21:22:01 +0000</pubDate>
      <link>https://dev.to/inthepocket/flutter-europe-2020-my-first-developer-conference-32dk</link>
      <guid>https://dev.to/inthepocket/flutter-europe-2020-my-first-developer-conference-32dk</guid>
      <description>&lt;p&gt;This year the first Flutter Europe conference took place in Warsaw (Poland) on January 23 and January 24. It was packed with interesting talks and it was awesome to meet other members of the Flutter community. I've learned a lot about animations, performance, augmented reality, building games, domain-driven-design, convincing companies and clients of Flutter's awesomeness, ... In this article I'll share some insights on what was undoubtedly one of the most amazing experiences of my life.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AyeF-D0A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/Dash%2520%26%2520Me.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AyeF-D0A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/Dash%2520%26%2520Me.jpg" alt="alt text" title="Dash &amp;amp; Me"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The talks
&lt;/h2&gt;

&lt;p&gt;There were 26 talks in total but 2 were always given at the same time, so you could only attend 13 talks in total. Sometimes it was very difficult to device which talk would be more interesting or fun. Luckily, all talks will soon be published on the &lt;a href="https://www.youtube.com/channel/UCOoJkGYV00nr3EpZOUeuN1Q"&gt;Flutter Europe YouTube channel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I've learned something useful from every talk I attended and I enjoyed all of them, but if I had to pick my 5 favourite talks it would be these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance: Optimizing your Flutter app&lt;/li&gt;
&lt;li&gt;Augmented Reality in Flutter&lt;/li&gt;
&lt;li&gt;Animations in Flutter Done Right&lt;/li&gt;
&lt;li&gt;Building games using Flame&lt;/li&gt;
&lt;li&gt;How to convince business to Flutter? Real-life cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately I missed out on some interesting talks. Apparently 'Implementing complex UI with Flutter' was an amazing talk. From what I've heard, Marcin Szałek really had the audience in the palm of his hand. He was able to explain this topic so well that creating complex UI didn't look so difficult or intimidating anymore. This is definitely the first talk I'm going to watch as soon as they're available online.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4Qtq5RNO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/Animations%2520Done%2520Right.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Qtq5RNO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/Animations%2520Done%2520Right.jpg" alt="alt text" title="Animations done right talk"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flutter community
&lt;/h2&gt;

&lt;p&gt;If you're anything like me you read Flutter articles, watch Flutter videos, listen to Flutter podcasts, ... on (almost) a daily basis. A lot of the more famous Flutter community members who create this content attended Flutter Europe. I'm really glad I was able to meet so many of my personal heroes and heroines, like Emily Fortuna, Filip Hráček, Matt Rešetár, Simon Lightfoot, Rémi Rousselet, Felix Angelov, Jorge Coca, Hillel Coren, the Codemagic team, ...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;They say you should never meet your heroes, but that's not true if your heroes/heroines are members of the Flutter community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was also amazing to meet a lot of other members of the Flutter community. On my plane to Warsaw the two people sitting next were also attending Flutter Europe, and I met a lot of others there in between the talks, during lunch or at the two parties.&lt;/p&gt;

&lt;p&gt;I was travelling by myself, but there was never a moment where I was alone. I'm usually an introvert but it was so easy to connect with other Flutter developers. I heard a lot of amazing stories about other people's experiences with Flutter and I'm glad I was also able to share my own.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eV8e24R_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/Community.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eV8e24R_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/Community.jpeg" alt="alt text" title="Flutter Europe group picture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The loot
&lt;/h2&gt;

&lt;p&gt;As I've already mentioned in the title, this was my first developer conference. I've only attended gaming conferences like Gamescom so far. When I go to one of those I know I'll bring home a backpack full of loot, a.k.a. gadgets, toys and merchandise. I didn't expect this to happen at Flutter Europe. I wanted to attend this conference for the knowledge and the networking opportunity, but secretly I hoped I' go home with one of those Dash plushies you see everywhere on social media...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You should be going to a conference for the knowledge instead of the loot, but I can't be the only Flutter developer who wanted to get his hands on a Dash bird plushie, right?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ever since I learned of their existence I knew I wanted one. I'm really glad attendees were able to earn one by completing a programming challenge. The challenge itself wasn't very hard, the most difficult part was typing on a German keyboard (the semicolon was very well hidden).&lt;/p&gt;

&lt;p&gt;Everyone who attended received a Flutter t-shirt and a goodiebag with a hat, a water bottle and some other things. If you visited the booths of GDG, Codemagic, LeanCode and the others, you were also able to get some other t-shirts, all kinds of stickers, ...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5aGMzQeP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/Dash%2520Plushie.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5aGMzQeP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/Dash%2520Plushie.png" alt="alt text" title="Dash plushie"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My personal highlights
&lt;/h2&gt;

&lt;p&gt;Because I wanted the complete Flutter Europe experience I decided to go to the first party, the one before the conference started. This party took place in a stupendous building called the 'Warsaw Spire'. When I was standing in line to get my badge, someone who seemed quite familiar came to stand next to me. It was Matt Rešetár, a.k.a. &lt;a href="https://www.youtube.com/channel/UCSIvrn68cUk8CS8MbtBmBkA"&gt;Resocoder&lt;/a&gt;. In my opinion, his Flutter videos are the best you can find on YouTube and it was an honour to meet him.&lt;/p&gt;

&lt;p&gt;At this party I was also finally able to meet Marie Jaksman in real life. You might know her if you're a member of the &lt;a href="https://codemagic.io/start/"&gt;Codemagic&lt;/a&gt; community. She introduced me to Łukasz Kosman, one of the organisers of Flutter Europe and also the co-founder of LeanCode. He gave the 'How to convince business to Flutter? Real-life cases' talk on the second day. Marie told him I was the person who wrote &lt;a href="https://blog.codemagic.io/flutter-vs-ios-android-reactnative-xamarin/"&gt;the article about Flutter vs. other mobile development frameworks&lt;/a&gt;. His reaction was amazing, but also very unexpected.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;He said that he had a slide in his presentation about an experiment I conducted for my article. I was flabbergasted (or should I say fluttergasted?).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was the first article I've ever written, but apparently it was quite popular. To my surprise, some of the people I met at Flutter Europe had read it. The fact that it was also a part of a presentation at Flutter Europe was definitely a highlight for me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EtDhN4NL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/My%2520Article%2520At%2520Flutter%2520Europe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EtDhN4NL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/My%2520Article%2520At%2520Flutter%2520Europe.jpg" alt="alt text" title="My article at Flutter Europe"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's another person worth mentioning who definitely made the whole Flutter Europe experience even better. His name is Dylan Beattie. The first time we saw him was at the party after the first day of the conference where he gave a performance. He turned Billy Joel's 'Piano Man' into 'Bug in the Javascript', Bon Jovi's 'You give love a bad name' into 'You give REST a bad name', ... If you're a programmer who's into the rock classics, you should definitely visit &lt;a href="https://www.youtube.com/user/dylanhat"&gt;his YouTube channel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Dylan also gave the last talk of the conference named 'The Art of Code'. He gave some amazing examples of why programming could/should be considered a form of art and at the end of the presentation he talked about a special project of his. Dylan is the creator of 'Rockstar', a programming language designed for creating computer programs that are also song lyrics. It's difficult to explain how amazing his talk was, but you can watch it &lt;a href="https://youtu.be/dMGlCUDNetc?t=2970"&gt;here&lt;/a&gt; and see for yourself. I can guarantee you that you'll enjoy it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6-hgIQeg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/Dylan%2520Beattie.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6-hgIQeg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bramdeconinck.com/Articles/Flutter/Flutter%2520Europe%25202020/Dylan%2520Beattie.png" alt="alt text" title="Dylan Beattie"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In my opinion, and I'm convinced a lot of other attendees agree with me, Flutter Europe 2020 was an amazing experience. This was only the first edition, but the organisers already got so many things right. Hopefully Flutter Europe 2021 will be even bigger and better. I'm happy I was able to attend and I can't wait for next year's edition!&lt;/p&gt;

</description>
      <category>fluttereurope</category>
      <category>flutter</category>
      <category>techtalks</category>
    </item>
    <item>
      <title>Localising Flutter applications and automating the localisation process</title>
      <dc:creator>Bram De Coninck</dc:creator>
      <pubDate>Thu, 30 Jan 2020 19:16:40 +0000</pubDate>
      <link>https://dev.to/inthepocket/localising-flutter-applications-and-automating-the-localisation-process-26b1</link>
      <guid>https://dev.to/inthepocket/localising-flutter-applications-and-automating-the-localisation-process-26b1</guid>
      <description>&lt;p&gt;Developing a small application or PoC in one language is acceptable, but as soon as you release an app to the public you might want to consider localising it first. I live in Belgium, a country best known for its chocolate, waffles, fries and beer. We have three official languages: Dutch, French and German. We also have English as an unofficial language. If I want to publish an app and I want as many Belgians as possible to be able to use it, it'll definitely need to support these languages.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;more languages and locales&lt;/strong&gt; your application supports, the &lt;strong&gt;bigger&lt;/strong&gt; your &lt;strong&gt;target audience&lt;/strong&gt; will be.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article I will guide you through localising your Flutter application using .arb (Application Resource Bundle) files and the Intl package. I will also show you how you can &lt;strong&gt;automatically&lt;/strong&gt; provide your app with the &lt;strong&gt;latest translations&lt;/strong&gt; using the Loco tool. I've written a &lt;strong&gt;shell script&lt;/strong&gt; that can be run locally or used by a &lt;strong&gt;CI/CD&lt;/strong&gt; tool of choice. I'll be using Codemagic because it's easy to set up and visualise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the dependencies to the project
&lt;/h2&gt;

&lt;p&gt;Before you can localise your Flutter app, you'll first need to add some dependencies to the pubspec.yaml file of your project.&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;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter&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_localizations&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;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;intl_translation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.17.7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Localisation in the MaterialApp
&lt;/h2&gt;

&lt;p&gt;Most of the setup for the localisation happens in the &lt;strong&gt;MaterialApp&lt;/strong&gt;, which is the heart of your Flutter app. It doesn't necessarily have to be a MaterialApp though, it can also be a CupertinoApp or a WidgetsApp. This is the place where the supported locales are defined and is decided which locale is going to be used in the app. It's also where we'll add our own localisations delegate, which is where the rest of the magic happens.&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;MyApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&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;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="s"&gt;'Localisation Demo'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;primarySwatch:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;home:&lt;/span&gt; &lt;span class="n"&gt;HelloWorld&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="c1"&gt;// List of all supported locales (language code followed by country code).&lt;/span&gt;
      &lt;span class="c1"&gt;// For this example, I'll be using Belgian English, Belgian Dutch and Belgian French.&lt;/span&gt;
      &lt;span class="c1"&gt;// The order of the supported locales is very important because a fallback mechanism is used.&lt;/span&gt;
      &lt;span class="nl"&gt;supportedLocales:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'BE'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'nl'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'BE'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'fr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'BE'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="c1"&gt;// These delegates are responsible for loading the translations of the selected locale.&lt;/span&gt;
      &lt;span class="nl"&gt;localizationsDelegates:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// This is where all translations are defined, will be added later.&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AppLocalizationsDelegate&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="c1"&gt;// Built-in delegate for the localisation of the Material widgets (e.g. tooltips).&lt;/span&gt;
        &lt;span class="n"&gt;GlobalMaterialLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// Built-in localisation for text direction (left-to-right or right-to-left).&lt;/span&gt;
        &lt;span class="n"&gt;GlobalWidgetsLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delegate&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="c1"&gt;// This is where it's decided which locale should be used.&lt;/span&gt;
      &lt;span class="nl"&gt;localeResolutionCallback:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Locale&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;supportedLocales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;supportedLocale&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;supportedLocales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// The language of the device of the user is compared to every supported language.&lt;/span&gt;
          &lt;span class="c1"&gt;// If the language codes match, the supported locale with that language code is chosen.&lt;/span&gt;
          &lt;span class="c1"&gt;// This allows users using American English or British English as locales&lt;/span&gt;
          &lt;span class="c1"&gt;// to be able to use the Belgian English localisation.&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;locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;languageCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;supportedLocale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;languageCode&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;supportedLocale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// If the language of the user isn't supported, the default locale should be used.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;supportedLocales&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;first&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the AppLocalizations class and the AppLocalizationsDelegate
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;AppLocalizations&lt;/strong&gt; class contains the logic to load the messages (= translations) of a given locale. It's also where you'll define all Intl messages that will be used in the app. All messages should be in the same language, the language of the &lt;strong&gt;'source locale'&lt;/strong&gt; of your project. In this case it's Belgian English.&lt;/p&gt;

&lt;p&gt;I've created two Intl messages: one for the app title and one for a 'hello world' message that we'll use later in the UI.&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;AppLocalizations&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Locale&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;countryCode&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;locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;
      &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;languageCode&lt;/span&gt;
      &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&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;String&lt;/span&gt; &lt;span class="n"&gt;localeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Intl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;canonicalizedLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&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;initializeMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;localeName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&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;Intl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultLocale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;localeName&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;AppLocalizations&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Localizations are usually accessed using the InheritedWidget "of" syntax.&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;AppLocalizations&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Localizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Messages which contain a string in the default language of your app and a name as identifier.&lt;/span&gt;
  &lt;span class="c1"&gt;// CamelCasing is required for the name of the Intl message.&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;appTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Intl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Localisation Demo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'appTitle'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;helloWorld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Intl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Hello world!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'helloWorld'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;AppLocalizationsDelegate&lt;/strong&gt; is the glue between the AppLocalizations class and the MaterialApp. The load function returns an AppLocalizations object as it contains all localised messages. The isSupported function can be used to check if a certain locale (or in this case language code) is supported.&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;AppLocalizationsDelegate&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;LocalizationsDelegate&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// As the instance of this delegate will never change, it can have a const constructor.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AppLocalizationsDelegate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Checks whether or not a certain locale (or language code in this cast) is supported.&lt;/span&gt;
  &lt;span class="c1"&gt;// The order of the locales doesn't matter in this case.&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Locale&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'nl'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'fr'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;languageCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Load the translations of a certain locale.&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Locale&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Defines whether or not all the app’s widgets should be reloaded when the load method is completed.&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;shouldReload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LocalizationsDelegate&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you copy/paste this code you'll get an error on the initializeMessages method, as it's not defined yet. This method will be generated later by the Intl package. Before you can start providing translations to your Flutter app, you first need to create a Loco project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Loco project
&lt;/h2&gt;

&lt;p&gt;Head over to &lt;a href="https://localise.biz/" rel="noopener noreferrer"&gt;Loco&lt;/a&gt; and create an account if you don't already have one. Loco has a generous free plan which allows you to create two projects. Create a new Loco project and make sure to select the same &lt;strong&gt;source locale&lt;/strong&gt; you've used for your Intl messages.&lt;/p&gt;

&lt;p&gt;Next, head over to 'Developer Tools' and click on 'API Keys'. This will open up a dialog in which you can create a 'Full access key' for your project. Make sure to save this key somewhere, as it's impossible to retrieve it later. In case you lose this key, you'll have to generate a new one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbramdeconinck.com%2FArticles%2FFlutter%2FLocalisation%2FLoco-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbramdeconinck.com%2FArticles%2FFlutter%2FLocalisation%2FLoco-1.png" title="Loco full access key" alt="Loco-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Flutter works together with Loco
&lt;/h2&gt;

&lt;p&gt;Your Flutter app will interact with Loco in two ways. The Intl messages you created in the AppLocalizations class will be imported into Loco. Everytime you add or update these translations your Loco project will get an update as well. New assets will be created in your source locale or existing assets will get a new value. The second way you use Loco is by downloading all translations for all locales in .arb format and then turning these files into .dart files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It doesn't have to be the job of the developer to provide the translations for the app, it can also be done by a product owner or a client for example.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In your Loco project you can choose additional locales you want to support, in my case Belgian Dutch and Belgian French. Translating messages for a new locale from your source locale can be done by a very user-friendly GUI. You don't need to be a technical person to add or edit translations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbramdeconinck.com%2FArticles%2FFlutter%2FLocalisation%2FLoco-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbramdeconinck.com%2FArticles%2FFlutter%2FLocalisation%2FLoco-2.png" title="Loco adding translation example" alt="Loco-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the localisation process
&lt;/h2&gt;

&lt;p&gt;Importing messages into Loco and exporting translations from Loco can be done manually, but it's a tedious process. These are the steps that need to be taken for the entire localisation process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate .arb files from the Intl messages defined in the AppLocalizations class.&lt;/li&gt;
&lt;li&gt;Import those .arb files into the Loco project to add/update the translations for the source locale.&lt;/li&gt;
&lt;li&gt;Fetch the latest translations for all locales in .arb format from the Loco project.&lt;/li&gt;
&lt;li&gt;Generate .dart files from these translations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's possible to automate this process with a simple shell script I've provided below. The only thing you need to do is provide a value for all variables: your  Loco API key, the path of the AppLocalizations class and the desired output path for the translations.&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 command&lt;/span&gt;

&lt;span class="c"&gt;# Variables&lt;/span&gt;
&lt;span class="nv"&gt;LOCO_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"LOCO_API_KEY"&lt;/span&gt;
&lt;span class="nv"&gt;APP_LOCALIZATIONS_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lib/app_localizations.dart"&lt;/span&gt;
&lt;span class="nv"&gt;OUTPUT_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lib/l10n"&lt;/span&gt;

&lt;span class="c"&gt;# Get packages&lt;/span&gt;
flutter packages get

&lt;span class="c"&gt;# Generate .arb files&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;l10n-input
flutter pub pub run intl_translation:extract_to_arb &lt;span class="nt"&gt;--output-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;l10n-input &lt;span class="nv"&gt;$APP_LOCALIZATIONS_PATH&lt;/span&gt;

&lt;span class="c"&gt;# Import translations into Loco&lt;/span&gt;
curl &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--data-binary&lt;/span&gt; &lt;span class="s2"&gt;"@l10n-input/intl_messages.arb"&lt;/span&gt; &lt;span class="s2"&gt;"https://localise.biz/api/import/arb?async=true&amp;amp;index=id&amp;amp;locale=en&amp;amp;key=&lt;/span&gt;&lt;span class="nv"&gt;$LOCO_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Export translations from Loco&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"translated.zip"&lt;/span&gt; &lt;span class="s2"&gt;"https://localise.biz/api/export/archive/arb.zip?key=&lt;/span&gt;&lt;span class="nv"&gt;$LOCO_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
unzip &lt;span class="nt"&gt;-qq&lt;/span&gt; &lt;span class="s2"&gt;"translated.zip"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"l10n-translated"&lt;/span&gt;

&lt;span class="c"&gt;# Create directory for output if it doesn't exist yet&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;$OUTPUT_PATH&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nv"&gt;$OUTPUT_PATH&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Generate Dart files with translations&lt;/span&gt;
flutter pub pub run intl_translation:generate_from_arb &lt;span class="nt"&gt;--output-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT_PATH&lt;/span&gt; &lt;span class="nt"&gt;--no-use-deferred-loading&lt;/span&gt; &lt;span class="nv"&gt;$APP_LOCALIZATIONS_PATH&lt;/span&gt; l10n-translated/&lt;span class="k"&gt;*&lt;/span&gt;/l10n/intl_messages_&lt;span class="k"&gt;*&lt;/span&gt;.arb

&lt;span class="c"&gt;# Cleanup&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;translated.zip
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; l10n-translated
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; l10n-input

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's possible to run this script locally but you can also integrate it into your CI/CD tool of choice. I've chosen to use Codemagic because it's easy to set up and use. Codemagic allows you to execute shell scripts before and after every phase of a build. It's important to execute this script before the build phase though. I've decide to run the script before the test phase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbramdeconinck.com%2FArticles%2FFlutter%2FLocalisation%2FCodemagic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbramdeconinck.com%2FArticles%2FFlutter%2FLocalisation%2FCodemagic.png" title="Codemagic adding the pre-test script" alt="Codemagic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using a CI/CD tool to fetch translations automatically will make sure users always have the most recent translations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reason it's so useful to integrate this script into a CI/CD tool is that with every build of the application the latest translations will be fetched automatically. A developer might forget to fetch the latest translations before releasing a new version of the Flutter app to the stores and it's possible that another person has added or updated translations on Loco.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using localised messages in the UI
&lt;/h2&gt;

&lt;p&gt;In the example below you can see how to get a localised message from the AppLocalizations class and use it in a Text widget. Try changing the language of your device without closing the app. When you open the app again, the widget will be rerendered automatically and display the message in the language you've selected (as long as it's supported, obviously).&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;HelloWorld&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&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;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using localised messages without context
&lt;/h2&gt;

&lt;p&gt;Using localised messages without context is possible but it's a bit hacky in my opinion. There's currently no other way to do it though.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It can sometimes be useful to get a localised message without using context, in a BLoC or a ViewModel for example.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;AppLocalizations&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;AppLocalizations&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Locale&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Locale&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;countryCode&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;locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;
      &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;languageCode&lt;/span&gt;
      &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&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;String&lt;/span&gt; &lt;span class="n"&gt;localeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Intl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;canonicalizedLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&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;initializeMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;localeName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&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;Intl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultLocale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;localeName&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;AppLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Localizations are usually accessed using the InheritedWidget "of" syntax&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;AppLocalizations&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Localizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AppLocalizations&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;I've added a static AppLocalizations object in the AppLocalizations class. It gets its value from the private constructor, which is used in the load method. As you can see in the example below, it's now possible to use localised messages without context.&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;HelloWorld&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&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;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Localising the app title
&lt;/h2&gt;

&lt;p&gt;It's also possible to localise the name of your application by using the &lt;strong&gt;onGenerateTitle&lt;/strong&gt; callback of the MaterialApp at the root of your project. Make sure you're not using the 'title' property of the MaterialApp.&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;MyApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&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;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;primarySwatch:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;home:&lt;/span&gt; &lt;span class="n"&gt;HelloWorld&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="c1"&gt;// Make sure you're not using the title property!&lt;/span&gt;
      &lt;span class="nl"&gt;onGenerateTitle:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AppLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;appTitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This approach makes localising your Flutter apps a lot easier. Developers create or adjust Intl messages in the source locale in the AppLocalizations class. Anyone can provide translations for those messages on Loco, it doesn't have to be the developer. Using the provided script and a CI/CD tool of choice will make sure the Flutter app and the Loco project are always synchronised. This allows the users of the Flutter app to always have the most recent translations.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>localization</category>
      <category>intl</category>
      <category>codemagic</category>
    </item>
    <item>
      <title>Flutter Versus Other Mobile Development Frameworks: A UI And Performance Experiment</title>
      <dc:creator>Bram De Coninck</dc:creator>
      <pubDate>Mon, 13 Jan 2020 22:21:15 +0000</pubDate>
      <link>https://dev.to/inthepocket/flutter-versus-other-mobile-development-frameworks-a-ui-and-performance-experiment-3el</link>
      <guid>https://dev.to/inthepocket/flutter-versus-other-mobile-development-frameworks-a-ui-and-performance-experiment-3el</guid>
      <description>&lt;p&gt;I’ve already read a lot of articles about how Flutter compares to other mobile development frameworks, but most of them were written from a theoretical point of view. It’s already been well established that Flutter is a solid choice for creating mobile applications and that it’s a worthy opponent to frameworks like React Native and Xamarin Forms.&lt;/p&gt;

&lt;p&gt;Everyone agrees that Flutter looks good on paper, but how can companies who still doubt Flutter be convinced that they should at least consider using it? Theories and opinions are one thing, but what we really need more of is proof of Flutter’s capabilities and performance.&lt;/p&gt;

&lt;p&gt;This year I graduated in Applied Computer Science at University College Ghent in Belgium. During my last year I was given the opportunity to write a thesis around a topic of my choice. I decided to write my thesis around Flutter because I hoped it would be one of the first of its kind. In the first section I discussed how Flutter works, what Dart is and why it’s ideal for Flutter and I also discussed a couple of state management techniques. In the second section I performed two experiments.&lt;/p&gt;

&lt;p&gt;During the first experiment I created the same app five times, each time with a different framework. The frameworks I used were native Android, native iOS, Flutter, Xamarin Forms and React Native. During the second experiment I measured the CPU usage of the apps I created in the first experiment. In this article I will share the results of these experiments.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating the same application five times
&lt;/h1&gt;

&lt;p&gt;I’m going to be honest with you, this first experiment wasn’t really fair. I don’t have the same amount of experience with every framework I used. If I were to make a ranking of the amount of experience I had with each framework from highest to lowest, it would probably look like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Android&lt;/li&gt;
&lt;li&gt;Flutter&lt;/li&gt;
&lt;li&gt;Xamarin Forms&lt;/li&gt;
&lt;li&gt;iOS&lt;/li&gt;
&lt;li&gt;React Native&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I do however want to point out that I had already created mock-ups of the application so I wouldn’t have to mess around with thinking about the UI during the development of the first app. I also knew enough about every framework to create the application without Googling a lot, so there wasn’t much of a learning curve.&lt;/p&gt;

&lt;p&gt;The name of the application I created is &lt;strong&gt;Cocktailr&lt;/strong&gt;. As the name indicates, this app is for cocktail lovers who want to discover new cocktails and also search for cocktails they can make with ingredients they have or want to use.The application exists out of four screens: a cocktail list screen, a cocktail detail screen, an ingredient list screen and a cocktail creation screen. The images underneath are the mock-ups I created before developing the applications. I don’t think I have to show you the screens of the five apps I created because, spoiler alert, they all look nearly identical to the mock-ups (apart from some minor inconsistencies which I’ll discuss later). Instead, I’m going to discuss what it was like to develop the app with each framework and also point out some of the difficulties I encountered. I’m also going to compare the amount of time it took to create the apps per framework.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oZsB3gm_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/4010/1%2Av5i7VDJaL5-UYUzvc9H5uA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oZsB3gm_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/4010/1%2Av5i7VDJaL5-UYUzvc9H5uA.jpeg" alt="alt text" title="Screens of the Cocktailr application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the native Android application
&lt;/h2&gt;

&lt;p&gt;To create the native Android application I used Android Studio as IDE and Kotlin as programming language. The 3rd party packages I relied on were Glide for fetching network images, Retrofit for consuming HTTP requests and GSON to convert JSON objects into POJOs (Plain Old Java Objects). I also used something called the Navigation Component, which is a part of Android Jetpack, a suite of libraries, tools, and guidance to help developers write high-quality apps easier. It was still in alpha at the time, but I loved it and I felt comfortable using it.&lt;/p&gt;

&lt;p&gt;To create layouts on Android you can either use a visual layout builder or you can create it in XML. Usually you’ll work with both to get the best of both worlds.&lt;/p&gt;

&lt;p&gt;The main problem I had (and still have) with Android development is that there are too many ways to accomplish a certain task. If you were to Google how to make network requests on Android you would discover all kinds of different packages and documentation. That’s why the Android developers at Google are currently working on Android Jetpack, the set of tools I mentioned earlier. This will make finding the right tools to solve your problems more straightforward. Since both Java and Kotlin are supported for Android development, you’re sometimes going to find code samples or tutorials written in the language you’re not using. Android Studio does have support for turning Java code into Kotlin code but the ‘translation’ isn’t always 100% accurate.&lt;/p&gt;

&lt;p&gt;Another difficult problem is that it can be quite hard to add elements to your layout dynamically (Jetpack Compose, which allows you to create layouts declaratively like with Dart, wasn’t around when I created this project). On the cocktail detail screen I wanted to add a table with the names of the ingredients of the cocktail and also a measurement per ingredient. I found it quite difficult to add rows to a table programmatically. There isn’t a lot of information about it in the official Android documentation, so when you have a problem like this you’re probably going to have to look for a solution on websites like StackOverflow. I was eventually able to add the rows programmatically but the solution was, in my opinion, quite cumbersome. The Kotlin code underneath is the function I wrote to create table rows programmatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createTableRows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tl_cocktail_detail_ingredients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAllViews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;params&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TableRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LayoutParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TableRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LayoutParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WRAP_CONTENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tv1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"INGREDIENT"&lt;/span&gt;
    &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ContextCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDrawable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_border&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPadding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layoutParams&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;
    &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gravity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CENTER&lt;/span&gt;
    &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTextColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContextCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typeface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Typeface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DEFAULT_BOLD&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tv2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"AMOUNT"&lt;/span&gt;
    &lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPadding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layoutParams&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;
    &lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gravity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gravity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CENTER&lt;/span&gt;
    &lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTextColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContextCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typeface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Typeface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DEFAULT_BOLD&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TableRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ContextCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDrawable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_border&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tl_cocktail_detail_ingredients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tr&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;cocktail&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;ingredients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;cocktail&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;measurements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cocktail&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingredients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEachIndexed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ingredient&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tv1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ingredient&lt;/span&gt;
            &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ContextCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDrawable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_border&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPadding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layoutParams&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;

            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tv2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;cocktail&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;measurements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cocktail&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;measurements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPadding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layoutParams&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;

            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TableRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ContextCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDrawable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_border&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tv1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tv2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;tl_cocktail_detail_ingredients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Something that isn’t really an issue but I also find quite cumbersome is how certain styling has to be done. Let’s say you want to create a text field with a border around it. To accomplish this, you’ll have to add the text field to the layout of your screen but you’ll also have to create a separate XML file to define the border. Then, you need to set that separate file as the background of the text field. It isn’t difficult at all, it just feels like a lot of work for such a simple task. The XML code underneath is set as background of the textfield to create a border around it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&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="nt"&gt;&amp;lt;shape&lt;/span&gt; &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt; &lt;span class="na"&gt;android:shape=&lt;/span&gt; &lt;span class="s"&gt;"rectangle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;solid&lt;/span&gt; &lt;span class="na"&gt;android:color=&lt;/span&gt;&lt;span class="s"&gt;"#ffffff"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;corners&lt;/span&gt; &lt;span class="na"&gt;android:radius=&lt;/span&gt;&lt;span class="s"&gt;"5dp"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;stroke&lt;/span&gt; &lt;span class="na"&gt;android:width=&lt;/span&gt;&lt;span class="s"&gt;"1dp"&lt;/span&gt; &lt;span class="na"&gt;android:color=&lt;/span&gt;&lt;span class="s"&gt;"#000000"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/shape&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the native iOS application
&lt;/h2&gt;

&lt;p&gt;To create the native iOS application I used Xcode as IDE and Swift as programming language. No 3rd party packages were used. I based my way of working on the book “App Development with Swift”, which is an official book by Apple.&lt;/p&gt;

&lt;p&gt;Even though I’m an Android developer at heart, I must say it’s a lot easier to find the best way to tackle a certain problem. There are for example less ways to add functionality like consuming HTTP requests, which makes it easier to find the solution that fits best for the needs of your project.&lt;/p&gt;

&lt;p&gt;Something that I had to figure out that wasn’t described in the book was how to subscribe to changes happening in the cocktail repository, the class were all cocktail objects are stored. I didn’t want to refetch all cocktails every time the cocktail list screen was opened. To solve this problem I used something called the NotificationCenter.&lt;/p&gt;

&lt;p&gt;Thanks to the book I mentioned earlier I hardly had to google anything. In fact, the article I found on NotificationCenter was the only source of information I used apart from the book. I was lucky that the article had an example on how to use it in Swift, but during the development of other applications I encountered the same issue I had on Android. Older articles or StackOverflow answers might have solutions provided in Objective-C, the language used for iOS development before Swift was introduced. Even though I didn’t have this issue during the development of the native iOS application for Cocktailr, I thought it was worth mentioning that it exists.&lt;/p&gt;

&lt;p&gt;My main issue with iOS development is that a Storyboard, the visual layout builder for iOS apps, can be more complex than necessary. For example, finding a setting to add a border around a text field can be very difficult. Luckily, it’s a lot easier to do this programmatically than it is in Android. SwiftUI, a way to create layouts declaratively with Swift, wasn’t around when I created this app by the way. It’s also possible to make changes to your layout in the XML file of a Storyboard, but unlike Android, the XML is a mess. The XML code underneath is the cocktail list screen, which actually only contains a list when you think about it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;scene&lt;/span&gt; &lt;span class="na"&gt;sceneID=&lt;/span&gt;&lt;span class="s"&gt;"aud-0C-1Pk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;objects&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tableViewController&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"3Sf-E8-Zfo"&lt;/span&gt; &lt;span class="na"&gt;customClass=&lt;/span&gt;&lt;span class="s"&gt;"CocktailTableViewController"&lt;/span&gt; &lt;span class="na"&gt;customModule=&lt;/span&gt;&lt;span class="s"&gt;"Cocktailr_iOS"&lt;/span&gt; &lt;span class="na"&gt;customModuleProvider=&lt;/span&gt;&lt;span class="s"&gt;"target"&lt;/span&gt; &lt;span class="na"&gt;sceneMemberID=&lt;/span&gt;&lt;span class="s"&gt;"viewController"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;tableView&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"view"&lt;/span&gt; &lt;span class="na"&gt;clipsSubviews=&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt; &lt;span class="na"&gt;contentMode=&lt;/span&gt;&lt;span class="s"&gt;"scaleToFill"&lt;/span&gt; &lt;span class="na"&gt;alwaysBounceVertical=&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt; &lt;span class="na"&gt;dataMode=&lt;/span&gt;&lt;span class="s"&gt;"prototypes"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"plain"&lt;/span&gt; &lt;span class="na"&gt;separatorStyle=&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt; &lt;span class="na"&gt;rowHeight=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; &lt;span class="na"&gt;estimatedRowHeight=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; &lt;span class="na"&gt;sectionHeaderHeight=&lt;/span&gt;&lt;span class="s"&gt;"28"&lt;/span&gt; &lt;span class="na"&gt;sectionFooterHeight=&lt;/span&gt;&lt;span class="s"&gt;"28"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"9Np-nI-tQC"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;rect&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"frame"&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"0.0"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"0.0"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"414"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"896"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;autoresizingMask&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"autoresizingMask"&lt;/span&gt; &lt;span class="na"&gt;widthSizable=&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt; &lt;span class="na"&gt;heightSizable=&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;color&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"backgroundColor"&lt;/span&gt; &lt;span class="na"&gt;white=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;alpha=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;colorSpace=&lt;/span&gt;&lt;span class="s"&gt;"custom"&lt;/span&gt; &lt;span class="na"&gt;customColorSpace=&lt;/span&gt;&lt;span class="s"&gt;"genericGamma22GrayColorSpace"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;prototypes&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;tableViewCell&lt;/span&gt; &lt;span class="na"&gt;clipsSubviews=&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt; &lt;span class="na"&gt;contentMode=&lt;/span&gt;&lt;span class="s"&gt;"scaleToFill"&lt;/span&gt; &lt;span class="na"&gt;preservesSuperviewLayoutMargins=&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt; &lt;span class="na"&gt;selectionStyle=&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt; &lt;span class="na"&gt;indentationWidth=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;reuseIdentifier=&lt;/span&gt;&lt;span class="s"&gt;"CocktailCell"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"qUV-tm-57K"&lt;/span&gt; &lt;span class="na"&gt;customClass=&lt;/span&gt;&lt;span class="s"&gt;"CocktailTableViewCell"&lt;/span&gt; &lt;span class="na"&gt;customModule=&lt;/span&gt;&lt;span class="s"&gt;"Cocktailr_iOS"&lt;/span&gt; &lt;span class="na"&gt;customModuleProvider=&lt;/span&gt;&lt;span class="s"&gt;"target"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;rect&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"frame"&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"0.0"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"28"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"414"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"44"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;autoresizingMask&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"autoresizingMask"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;tableViewCellContentView&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"contentView"&lt;/span&gt; &lt;span class="na"&gt;opaque=&lt;/span&gt;&lt;span class="s"&gt;"NO"&lt;/span&gt; &lt;span class="na"&gt;clipsSubviews=&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt; &lt;span class="na"&gt;multipleTouchEnabled=&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt; &lt;span class="na"&gt;contentMode=&lt;/span&gt;&lt;span class="s"&gt;"center"&lt;/span&gt; &lt;span class="na"&gt;preservesSuperviewLayoutMargins=&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt; &lt;span class="na"&gt;insetsLayoutMarginsFromSafeArea=&lt;/span&gt;&lt;span class="s"&gt;"NO"&lt;/span&gt; &lt;span class="na"&gt;tableViewCell=&lt;/span&gt;&lt;span class="s"&gt;"qUV-tm-57K"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"4Hj-H9-rtF"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;rect&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"frame"&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"0.0"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"0.0"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"414"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"43.5"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;autoresizingMask&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"autoresizingMask"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/tableViewCellContentView&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;connections&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;segue&lt;/span&gt; &lt;span class="na"&gt;destination=&lt;/span&gt;&lt;span class="s"&gt;"IYp-bk-shv"&lt;/span&gt; &lt;span class="na"&gt;kind=&lt;/span&gt;&lt;span class="s"&gt;"show"&lt;/span&gt; &lt;span class="na"&gt;identifier=&lt;/span&gt;&lt;span class="s"&gt;"CocktailDetailSegue"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"bjc-Sd-8ni"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/connections&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/tableViewCell&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/prototypes&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;connections&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;outlet&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"dataSource"&lt;/span&gt; &lt;span class="na"&gt;destination=&lt;/span&gt;&lt;span class="s"&gt;"3Sf-E8-Zfo"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"Arh-Wp-anV"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;outlet&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"delegate"&lt;/span&gt; &lt;span class="na"&gt;destination=&lt;/span&gt;&lt;span class="s"&gt;"3Sf-E8-Zfo"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"qJE-tE-xa7"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/connections&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/tableView&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;navigationItem&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"navigationItem"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Cocktailr"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"0Li-fu-c3B"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;barButtonItem&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"backBarButtonItem"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"4vq-K8-peJ"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;barButtonItem&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"rightBarButtonItem"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Search"&lt;/span&gt; &lt;span class="na"&gt;image=&lt;/span&gt;&lt;span class="s"&gt;"Search Icon"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"fCh-D0-Tws"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;connections&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;selector=&lt;/span&gt;&lt;span class="s"&gt;"searchBarButtonClick:"&lt;/span&gt; &lt;span class="na"&gt;destination=&lt;/span&gt;&lt;span class="s"&gt;"3Sf-E8-Zfo"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"0Pk-I0-Hsb"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/connections&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/barButtonItem&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/navigationItem&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;connections&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;segue&lt;/span&gt; &lt;span class="na"&gt;destination=&lt;/span&gt;&lt;span class="s"&gt;"UPM-k4-dYF"&lt;/span&gt; &lt;span class="na"&gt;kind=&lt;/span&gt;&lt;span class="s"&gt;"show"&lt;/span&gt; &lt;span class="na"&gt;identifier=&lt;/span&gt;&lt;span class="s"&gt;"SearchTableViewSegue"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"e9G-mk-bUI"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/connections&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/tableViewController&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;placeholder&lt;/span&gt; &lt;span class="na"&gt;placeholderIdentifier=&lt;/span&gt;&lt;span class="s"&gt;"IBFirstResponder"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"dkZ-bW-bEE"&lt;/span&gt; &lt;span class="na"&gt;userLabel=&lt;/span&gt;&lt;span class="s"&gt;"First Responder"&lt;/span&gt; &lt;span class="na"&gt;sceneMemberID=&lt;/span&gt;&lt;span class="s"&gt;"firstResponder"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/objects&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;point&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"canvasLocation"&lt;/span&gt; &lt;span class="na"&gt;x=&lt;/span&gt;&lt;span class="s"&gt;"886"&lt;/span&gt; &lt;span class="na"&gt;y=&lt;/span&gt;&lt;span class="s"&gt;"-457"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/scene&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I encountered a problem during the creation of the cocktail list page. The list, or TableView as it’s called on iOS, exists out of custom cells. Each cell contains the information of one cocktail: an image, its name and its ID. When you scroll through the list of cocktails you’ll see that all of a sudden the cocktail names don’t match with the images anymore. This happens because the cells of the list are reused when you scroll through the list and it takes a bit longer to load an image than it does to load text. Some iOS developers have told me this is an issue all junior iOS developers encounter and that it’s quite easy to fix. I thought it was still worth mentioning, as I didn’t have a similar problem with any of the other frameworks.&lt;/p&gt;

&lt;p&gt;I also had to use a TableView to create the table on the cocktail detail page, as there isn’t an iOS component to create tables. The name ‘TableView’ can be quite misleading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Flutter application
&lt;/h2&gt;

&lt;p&gt;To create the Flutter application I used Visual Studio Code as IDE and Dart as programming language. The 3rd party packages I relied on were Image_picker for taking photos, Http for consuming HTTP requests and RxDart to use Observables for data streams. I used the BLoC state management technique for this application, but I didn’t use the 3rd party package. I found all of the information I needed to create this application in the official Flutter docs and in articles about BLoC written by Didier Boelens.&lt;/p&gt;

&lt;p&gt;Flutter doesn’t have a visual layout builder. You also don’t need an additional language like XML to create your layout. You use Dart to create widgets which together will form your layouts, and you build these layouts declaratively.&lt;/p&gt;

&lt;p&gt;The biggest problem I faced during the development of this application was state management. I didn’t have a lot of experience with BLoC at the time and the Provider package wasn’t around when I created this application. BLoC is, in my opinion and as regularly stated by Google, the superior state management technique. It has a huge learning curve to it though. I didn’t have any experience with Redux or Scoped_Model either, and only using setState() wasn’t going to suffice, so I had to resort to BLoC.&lt;/p&gt;

&lt;p&gt;Flutter provides its own widgets, and it has a lot of them! One of them is a Table widget, which made it really easy to create the table on the cocktail detail page. I had problems creating this table with both Android and iOS, so this was a nice change of pace. The code underneath is the function to create all the table rows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TableRow&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_createTable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Cocktail&lt;/span&gt; &lt;span class="n"&gt;cocktail&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TableRow&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tableList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[];&lt;/span&gt;
  &lt;span class="n"&gt;tableList&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;TableRow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"INGREDIENT"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;textAlign:&lt;/span&gt; &lt;span class="n"&gt;TextAlign&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"MEASUREMENT"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;textAlign:&lt;/span&gt; &lt;span class="n"&gt;TextAlign&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&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;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for&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;i&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cocktail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ingredients&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="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tableList&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;TableRow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
          &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cocktail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ingredients&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]),&lt;/span&gt;
            &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="o"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cocktail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;measurements&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="n"&gt;cocktail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;measurements&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&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="nl"&gt;padding:&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&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;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="n"&gt;tableList&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;The only other ‘issue’ I encountered is that the iOS app doesn’t have a native look-and-feel. I used the Material widgets to create the application. Native Android apps implement Material design, but native iOS apps implement Cupertino design instead. A lot of widgets do look native on both platforms though, like the List widget for example. Other widgets like the AppBar widget don’t. Some widgets like the Switch widget have an adaptive constructor, which allows you to create a switch that look native on both platforms. It’s possible to create separate widgets for Android and iOS for widgets without an adaptive constructor, but unfortunately that costs more time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the React Native application
&lt;/h2&gt;

&lt;p&gt;To create the React Native application I used Visual Studio Code as IDE and TypeScript as programming language. I relied on a lot of 3rd party packages to create this application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expo to make the development of the application easier and because it has picture taking support built-in.&lt;/li&gt;
&lt;li&gt;React-native-easy-toasts for showing a toast on the cocktail creation screen.&lt;/li&gt;
&lt;li&gt;React-native-elements because it has a lot of beautiful UI components.&lt;/li&gt;
&lt;li&gt;React-native-svg-transformer to be able to use SVG icons.&lt;/li&gt;
&lt;li&gt;React-navigation for the navigation of the app.&lt;/li&gt;
&lt;li&gt;Styled-components to make it easier to style components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React Native doesn’t have a visual layout builder. You use a language called JSX to create your layouts declaratively.&lt;/p&gt;

&lt;p&gt;Managing state was one of the biggest challenges I encountered during the creation of this application. I didn’t know Redux, and learning it would have been a big time investment. This was not the fault of React Native, it was my own fault.&lt;/p&gt;

&lt;p&gt;The only styling issue I encountered was during the creation of the cards used in the list of the cocktail list screen. To create shadows on this card for Android I had to use the ‘elevation’ property. To have the same effect on iOS I had to use the ’shadow-color’, ’shadow-radius’, ’shadow-opacity’ and ’shadow-offset’ properties. Having to do styling in two different ways to have the same result on both Android and iOS isn’t really great when your goal is to make a cross-platform app. The code underneath shows the style I created for iOS and the elevation property I used for Android. It also shows the CocktailCard as a whole, written in JSX.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;cardWrapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;shadowColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#000000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;shadowOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;shadowRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;shadowOpacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;CocktailCard&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PureComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPress&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CocktailCardWrapper&lt;/span&gt; &lt;span class="nx"&gt;elevation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardWrapper&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onPress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CocktailImage&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CocktailText&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CocktailName&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CocktailName&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CocktailId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CocktailId&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CocktailText&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CocktailCardWrapper&lt;/span&gt;&lt;span class="err"&gt;&amp;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;The Android and iOS app created with React Native did have a native look-and-feel. That’s because React Native uses widgets of the platform it’s running on instead of providing its own widgets like Flutter does.&lt;/p&gt;

&lt;p&gt;Both Flutter and React Native support Stateful Hot Reload, but I have to say that it’s faster on Flutter than on React Native. On React Native it took longer to reload the app and sometimes it wouldn’t even work at all. The fact that it took longer might have something to do with the use of Expo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Xamarin Forms application
&lt;/h2&gt;

&lt;p&gt;To create the Xamarin Forms application I used Visual Studio for Mac as IDE and C# as programming language. The only 3rd party packages I relied on was Xam.Plugin.Media for taking pictures.&lt;br&gt;
Xamarin Forms has a visual layout builder, but it isn’t as good as the ones you use for native Android and native iOS development. You’ll do most of the layout building with the XAML language, which is very similar to XML.&lt;/p&gt;

&lt;p&gt;When I created this application, Xamarin Forms didn’t support Hot Reload. At the time of writing this article it’s either in beta or already supported. I don’t know how well it performs in comparison to the Hot Reload of Flutter and React Native though.&lt;/p&gt;

&lt;p&gt;Xamarin Forms is a cross-platform framework but when it comes to styling it doesn’t seem to be as straightforward as Flutter or React Native. I wanted to change the colour of the app bar to red. Apparently this isn’t a change you can make in the shared codebase, you need to change the colour of the app bar on Android and iOS separately. This is also the case for the colour of the selected item in the bottom navigation bar. Because you have to do the same styling twice, you might end up with inconsistencies between your Android and iOS applications if you’re not careful.&lt;/p&gt;

&lt;p&gt;Now that we’re on the topic of colours, I was unable to change the colour of the search icon in the app bar on the cocktail list screen and the camera icon of the cocktail detail screen. There is probably a way to change these colours, but it’s not straightforward at all. The images underneath illustrate the colour inconsistencies between Android and iOS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9_xu9efi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/4432/1%2An4KHC5oXb4etp8YU830N4w.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9_xu9efi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/4432/1%2An4KHC5oXb4etp8YU830N4w.jpeg" alt="alt text" title="Colour inconsistency in the Xamarin Forms application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The biggest issue I encountered was, again, the table on the cocktail detail screen. The styling of the table wasn’t the issue though. I have to iterate over the list of ingredients and the list of measurements, but iterating over two lists isn’t possible to create your layout. I had to create a new model which contains the ingredient and its measurement. Then I was able to iterate over a list of those objects. This is the only framework where I had to change the models in such a way and it’s very cumbersome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion of the first experiment
&lt;/h2&gt;

&lt;p&gt;The table below shows how much time it took to create every screen per framework. As you can see, I was able to build the Flutter application and the React Native application faster than either one of the native applications. You also have to keep in mind that because these are both cross-platform frameworks I was able to make an Android and iOS application in less time than one native application.&lt;/p&gt;

&lt;p&gt;Creating the Flutter application only took approximately ⅓ of the time it took to create both native applications combined. This proves how much time you save by using cross-platform frameworks. Their Hot Reload functionality also sped up the development cycle a lot.&lt;br&gt;
Both the Flutter and the React Native applications look nearly identical to the mock-ups. The Xamarin Forms application has some minor yet visible differences.&lt;/p&gt;

&lt;p&gt;I was able to create the Flutter app faster than the React Native app, but that’s because I had more experience with Flutter than with React Native. I also didn’t know how to manage state properly in the React Native. That’s why declare both Flutter and React Native the winners of this experiment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pmCgJDLH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/4076/1%2A2gXtjNT3wha6V8dOkgdyRg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pmCgJDLH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/4076/1%2A2gXtjNT3wha6V8dOkgdyRg.png" alt="alt text" title="Table with data of how long it took to create each application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Measuring the performance of the Android applications
&lt;/h1&gt;

&lt;p&gt;This experiment will determine the performance of the Android applications created during the first experiment. To conduct this experiment, I used the Android Profiler, a tool built into Android Studio. This tool allows you to collect data about CPU-usage, network activity and memory/battery consumption. I chose to focus on the CPU-usage as performance indicator for this experiment.&lt;/p&gt;

&lt;p&gt;It’s best to use a physical device for this experiment. I used the smartphone the smartphone I owned at the time, a OnePlus 6T with the following specifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operating System: Android 9.0 (Pie); OxygenOS 9.0.13&lt;/li&gt;
&lt;li&gt;Chipset: Qualcomm SDM845 Snapdragon 845 (10nm)&lt;/li&gt;
&lt;li&gt;CPU: Octa-core (4x2.8 GHz Kryo 385 Gold &amp;amp; 4x1.7 GHz Kryo 385 Silver)&lt;/li&gt;
&lt;li&gt;GPU: Adreno 630&lt;/li&gt;
&lt;li&gt;Memory: 8 GB RAM&lt;/li&gt;
&lt;li&gt;Storage: 128 GB ROM&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the experiment was conducted
&lt;/h2&gt;

&lt;p&gt;When conducting this experiment, a certain route was followed in every application and in the meantime the Android Profiler kept track of the CPU-usage. This tool then generated graphs to visualise the data it gathered. These steps were followed in every application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the app and wait until the cocktails are loaded in&lt;/li&gt;
&lt;li&gt;Start measuring the CPU-usage&lt;/li&gt;
&lt;li&gt;Scroll down to the bottom of the list of cocktails&lt;/li&gt;
&lt;li&gt;Scroll back up to the first cocktail in the list&lt;/li&gt;
&lt;li&gt;Click the first cocktail in the list&lt;/li&gt;
&lt;li&gt;Return to the list of cocktails&lt;/li&gt;
&lt;li&gt;Open the list of ingredients&lt;/li&gt;
&lt;li&gt;Scroll down to the bottom of the list of ingredients&lt;/li&gt;
&lt;li&gt;Enter ‘Gin’ into the search field&lt;/li&gt;
&lt;li&gt;Click on the ‘Gin’ ingredient. (This brings you back to the cocktail list screen.)&lt;/li&gt;
&lt;li&gt;Scroll down to the bottom of the list of cocktails again&lt;/li&gt;
&lt;li&gt;Scroll back up to the first cocktail in the list again&lt;/li&gt;
&lt;li&gt;Click the first cocktail in the list&lt;/li&gt;
&lt;li&gt;Return to the list of cocktails&lt;/li&gt;
&lt;li&gt;Click on the ‘suggestion’ button in the bottom navigation bar&lt;/li&gt;
&lt;li&gt;Write the word ‘description’ as description&lt;/li&gt;
&lt;li&gt;Click on the camera button, grant permissions and take a picture&lt;/li&gt;
&lt;li&gt;Tap the ‘Make suggestion’ button&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Preparations for the experiment
&lt;/h2&gt;

&lt;p&gt;I was able to install the native Android application correctly on my device using Android Studio, which was very easy. Correctly installing the other applications wasn’t as straightforward. To install the Flutter application for example, I had to use the command flutter run — profile. I wasn’t able to use a debug build because the Flutter application would have been compiled JIT (Just In Time, used for debugging) instead of AOT (Ahead Of Time, used for release and profile builds). This would have tempered the performance of the application.&lt;/p&gt;

&lt;p&gt;In the Xamarin Forms project I enabled ‘Enable developer instrumentation (debugging and profiling)’ before installing the application. To install the React Native application I first had to start Expo and then enable profiling and install the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results of the experiment
&lt;/h2&gt;

&lt;p&gt;The graphs underneath were generated by the Android Profiler. The green waves represent the CPU-usage of the application. The dark blue waves represent the CPU-usage of other processes. The vertical lines represent the moments in time where started and stopped measuring the CPU-usage.&lt;/p&gt;

&lt;p&gt;Take a moment to look back at the steps I followed in every application to notice that certain actions were more CPU-intensive than others. The spike you see near the beginning of every graph is when I started scrolling in the list of cocktails. The spike near the middle of the graph is when I started scrolling in the list of cocktails again, but this time all cocktails with gin as ingredient were shown. That list has more cocktails in it than the original one.&lt;/p&gt;

&lt;p&gt;CPU-usage of the native Android application:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rd76-Ekn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/3200/0%2A-I_ssHXqIms14cSL" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rd76-Ekn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/3200/0%2A-I_ssHXqIms14cSL" alt="alt text" title="CPU-usage of the native Android application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CPU-usage of the Flutter application:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kmFCyvlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/3200/0%2AtP84cuRhuKHaSfE_" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kmFCyvlO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/3200/0%2AtP84cuRhuKHaSfE_" alt="alt text" title="CPU-usage of the Flutter application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CPU-usage of the React Native application:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xunbZAc1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/3200/0%2Al66Dp40S525IojQA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xunbZAc1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/3200/0%2Al66Dp40S525IojQA" alt="alt text" title="CPU-usage of the React Native application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CPU-usage of the Xamarin Forms application:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--we73KYhJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/3200/0%2ALXZdtEV27nYq4Djv" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--we73KYhJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/3200/0%2ALXZdtEV27nYq4Djv" alt="alt text" title="CPU-usage of the Xamarin Forms application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion of the second experiment
&lt;/h2&gt;

&lt;p&gt;The table below contains statistics based on the data off the graphs that were generated by the Android Profiler. The first thing that stood out to me is that every application has a minimum CPU-usage of 0%. This happened when I opened the cocktail creation screen. At that time, the application is in a static state, causing the CPU temporarily unused.&lt;/p&gt;

&lt;p&gt;The second thing that stood out to me is that the 4 apps can be split into 2 groups. The CPU-usage of the Flutter application peers with the CPU-usage of the native Android application, while the CPU-usage of the React Native application matches the CPU-usage of the Xamarin Forms application.&lt;/p&gt;

&lt;p&gt;Before conducting this experiment, I had anticipated that the native Android application would have been the most performant one and the apps of the cross-platform frameworks would follow. For the most part this is true, but the Flutter application is an exception to this. The Flutter application was even slightly more economical on the CPU.&lt;/p&gt;

&lt;p&gt;When I was conducting the experiment it was already noticeable to me that the Xamarin Forms and the React Native application struggled when I scrolled through the list of cocktails. I think this is because of all of the cocktail images that needed to be downloaded. The Xamarin Forms application also struggled a bit when I was scrolling down the list of ingredients, which only had text and no images.&lt;/p&gt;

&lt;p&gt;It’s obvious that the CPU-usage of the Flutter application is smaller than that of the React Native and Xamarin Forms applications, and that’s why I declare Flutter the winner of this experiment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pu1_lmYp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/2752/1%2AZU1AcsFX_cMgvhmxVToS5A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pu1_lmYp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/2752/1%2AZU1AcsFX_cMgvhmxVToS5A.png" alt="alt text" title="CPU-usages of the Android applications"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Is Flutter more performant than React Native and Xamarin Forms?
&lt;/h1&gt;

&lt;p&gt;In the second experiment it was concluded that the Flutter application was less taxing on the CPU than the React Native and Xamarin Forms applications. This doesn’t automatically prove that Flutter is more performant than React Native or Xamarin Forms.&lt;/p&gt;

&lt;p&gt;There are a lot of features that the Cocktailr app doesn’t have, like caching for example. That would reduce the amount of network requests that need to be made and would make the app more performant, meaning that it could lead to different results. I also only used CPU-usage as a performance indicator. I don’t have any data on the memory or battery consumption for example. These are also an important performance indicators.&lt;/p&gt;

&lt;p&gt;I didn’t know beforehand what the results of this experiment were going to be. All I hoped to prove is that Flutter can stands its ground against other cross-platform frameworks, and I think I achieved that goal.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>xamarin</category>
      <category>reactnative</category>
      <category>android</category>
    </item>
  </channel>
</rss>
