<?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: Novaloop Ltd</title>
    <description>The latest articles on DEV Community by Novaloop Ltd (@novaloop).</description>
    <link>https://dev.to/novaloop</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%2Forganization%2Fprofile_image%2F2637%2F60a084a9-91b4-433a-a00a-08cfe49b629e.png</url>
      <title>DEV Community: Novaloop Ltd</title>
      <link>https://dev.to/novaloop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/novaloop"/>
    <language>en</language>
    <item>
      <title>My first Flutter project as an long time Angular developer</title>
      <dc:creator>Markus Huggler</dc:creator>
      <pubDate>Tue, 20 Sep 2022 08:32:09 +0000</pubDate>
      <link>https://dev.to/novaloop/my-first-flutter-project-as-an-long-time-angular-developer-2fgd</link>
      <guid>https://dev.to/novaloop/my-first-flutter-project-as-an-long-time-angular-developer-2fgd</guid>
      <description>&lt;p&gt;I am a full stack software engineer with 10 years of experience with .Net (Core) and Angular (I have started with AngularJS).&lt;/p&gt;

&lt;p&gt;We, at &lt;a href="https://www.novaloop.ch/en" rel="noopener noreferrer"&gt;Novaloop&lt;/a&gt; needed an MVP (or rather a working prototype) and we decided to implement it in Flutter.  We chose Flutter mainly because we wanted to be quick and we wanted the MVP to potentially run on all different platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The search for a new community
&lt;/h2&gt;

&lt;p&gt;As a long time user of the .Net Framework and Angular, I am very familiar with influential people in the community that create good content. This is definitely not the case for Flutter. &lt;/p&gt;

&lt;p&gt;It's not easy coming from the outside into a new community and catching up for several years. &lt;/p&gt;

&lt;p&gt;There is a very active Discord channel (&lt;a href="https://discord.com/invite/N7Yshp4" rel="noopener noreferrer"&gt;https://discord.com/invite/N7Yshp4&lt;/a&gt;) and also a lot going on in the Subreddit (&lt;a href="https://www.reddit.com/r/FlutterDev/" rel="noopener noreferrer"&gt;https://www.reddit.com/r/FlutterDev/&lt;/a&gt;). Many developers are also very active on Twitter. &lt;/p&gt;

&lt;p&gt;I have put together a Twitter list: &lt;a href="https://twitter.com/i/lists/1540588470950846466" rel="noopener noreferrer"&gt;https://twitter.com/i/lists/1540588470950846466&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here are a few selected Twitter accounts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Twitter Community
&lt;/h3&gt;

&lt;p&gt;Hint: The Flutter community handles use the blue heart emoji extensively ;)&lt;/p&gt;

&lt;p&gt;No particular order:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Twitter Handle&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/RydMike" rel="noopener noreferrer"&gt;@RydMike&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Learn a lot about theming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/vandadnp" rel="noopener noreferrer"&gt;@vandadnp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Flutter courses on YouTube&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/RobertBrunhage" rel="noopener noreferrer"&gt;@RobertBrunhage&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Great Flutter courses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/felangelov" rel="noopener noreferrer"&gt;@felangelov&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Author of the Bloc library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/vgventures" rel="noopener noreferrer"&gt;@vgventures&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Maintainer of the Bloc library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/remi_rousselet" rel="noopener noreferrer"&gt;@remi_rousselet&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Author of Provider and Riverpod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/biz84" rel="noopener noreferrer"&gt;@biz84&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Great Flutter courses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/ThomasBurkhartB" rel="noopener noreferrer"&gt;@ThomasBurkhartB&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Author of get_it dependency injection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/mhadaily" rel="noopener noreferrer"&gt;@mhadaily&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Community leader and organizer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/verry_codes" rel="noopener noreferrer"&gt;@verry_codes&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Flutterista and community leader Flutter DACH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/letsgetwckd" rel="noopener noreferrer"&gt;@letsgetwckd&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Great Flutter courses (flutterly)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/resocoder" rel="noopener noreferrer"&gt;@resocoder&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Great flutter courses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/devangelslondon" rel="noopener noreferrer"&gt;@devangelslondon&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Community leader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/ASalvadorini" rel="noopener noreferrer"&gt;@ASalvadorini&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Flutter speaker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/roaakdm" rel="noopener noreferrer"&gt;@roaakdm&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Flutterista and Flutter Vikings speaker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/CFDevelop" rel="noopener noreferrer"&gt;@CFDevelop&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Flutter Dev&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://twitter.com/luke_pighetti" rel="noopener noreferrer"&gt;@luke_pighetti&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Flutter GDE (Genuinely Disturbed Engineer)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Learning Resources
&lt;/h3&gt;

&lt;p&gt;Of course, and everyone will say this, the best resource is the official site and the respective documentation of the packages.&lt;/p&gt;

&lt;p&gt;But on my way to the first Flutter project, the following materials helped me a lot.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flutter documentation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.flutter.dev" rel="noopener noreferrer"&gt;https://docs.flutter.dev&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decoding Flutter&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/playlist?list=PLjxrf2q8roU1fRV40Ec8200rX6OuQkmnl" rel="noopener noreferrer"&gt;https://www.youtube.com/playlist?list=PLjxrf2q8roU1fRV40Ec8200rX6OuQkmnl&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter widget of the week&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG" rel="noopener noreferrer"&gt;https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Boring Show&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/playlist?list=PLjxrf2q8roU3ahJVrSgAnPjzkpGmL9Czl" rel="noopener noreferrer"&gt;https://www.youtube.com/playlist?list=PLjxrf2q8roU3ahJVrSgAnPjzkpGmL9Czl&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code with Andrea, very comprehensive Flutter courses&lt;/td&gt;
&lt;td&gt;&lt;a href="https://codewithandrea.com/" rel="noopener noreferrer"&gt;https://codewithandrea.com/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter Community @ Reddit&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/r/FlutterDev/" rel="noopener noreferrer"&gt;https://www.reddit.com/r/FlutterDev/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter Community @ Youtube&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/c/FlutterCommunityVideos" rel="noopener noreferrer"&gt;https://www.youtube.com/c/FlutterCommunityVideos&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter @ Youtube&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/c/flutterdev" rel="noopener noreferrer"&gt;https://www.youtube.com/c/flutterdev&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutterfly @ Youtube&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/c/Flutterly" rel="noopener noreferrer"&gt;https://www.youtube.com/c/Flutterly&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Majid Hajian @ Youtube&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/c/mhadaily" rel="noopener noreferrer"&gt;https://www.youtube.com/c/mhadaily&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reso Coder @ Youtube&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/c/ResoCoder" rel="noopener noreferrer"&gt;https://www.youtube.com/c/ResoCoder&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vandad Nahavandipoor @ Youtube&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/c/VandadNP" rel="noopener noreferrer"&gt;https://www.youtube.com/c/VandadNP&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Robert Brunhage @ Youtube&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/c/RobertBrunhage" rel="noopener noreferrer"&gt;https://www.youtube.com/c/RobertBrunhage&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Robert Brunhage Courses&lt;/td&gt;
&lt;td&gt;&lt;a href="https://robertbrunhage.com/" rel="noopener noreferrer"&gt;https://robertbrunhage.com/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FilledStacks @ Youtube&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/c/FilledStacks/featured" rel="noopener noreferrer"&gt;https://www.youtube.com/c/FilledStacks/featured&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And following Udemy courses I bought and watched individual chapters when needed during the development.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dart &amp;amp; Flutter - Zero to Mastery [2022] + Clean Architecture (German)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.udemy.com/course/dart-flutter-leicht-gemacht/" rel="noopener noreferrer"&gt;https://www.udemy.com/course/dart-flutter-leicht-gemacht/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter &amp;amp; Dart - The Complete Guide [2022 Edition]&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/" rel="noopener noreferrer"&gt;https://www.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Complete 2021 Flutter Development Bootcamp with Dart&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.udemy.com/course/flutter-bootcamp-with-dart/" rel="noopener noreferrer"&gt;https://www.udemy.com/course/flutter-bootcamp-with-dart/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter - Intermediate&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.udemy.com/course/flutter-intermediate/" rel="noopener noreferrer"&gt;https://www.udemy.com/course/flutter-intermediate/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dart and Flutter: The Complete Developer's Guide&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.udemy.com/course/dart-and-flutter-the-complete-developers-guide/" rel="noopener noreferrer"&gt;https://www.udemy.com/course/dart-and-flutter-the-complete-developers-guide/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  First steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dart
&lt;/h3&gt;

&lt;p&gt;Coming from C# and Typescript, Dart wasn't too much of a hurdle. Long story short: I learned to love Dart.&lt;/p&gt;

&lt;p&gt;Sure, compared to Typescript, certain tasks are more tedious, e.g. parsing JSON responses to objects. But the static analysis over the project works very well and is worth its weight in gold ;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything is a Widget
&lt;/h3&gt;

&lt;p&gt;At the beginning it takes a bit of getting used to, but with time it actually becomes very natural. &lt;/p&gt;

&lt;p&gt;Flutter's declarative widget system has allowed us to quickly and easily implement specifications from UX designers. At a pace that I have never seen before.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture and Code Organization
&lt;/h3&gt;

&lt;p&gt;As with many things, Flutter is not very opiniated. It is up to the team to choose the appropriate architecture and also to determine the appropriate solution for the organization of the code. &lt;/p&gt;

&lt;p&gt;Feature-first, layer-first - your choice&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;I'm used to the framework already bringing solutions and making it easy for a developer to ship different environments. To my knowledge, there is no official solution. &lt;/p&gt;

&lt;p&gt;Andrea has published a good article with a suggestion: &lt;a href="https://codewithandrea.com/articles/flutter-api-keys-dart-define-env-files/" rel="noopener noreferrer"&gt;https://codewithandrea.com/articles/flutter-api-keys-dart-define-env-files/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hot Topics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  State Management
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.flutter.dev/development/data-and-backend/state-mgmt/options" rel="noopener noreferrer"&gt;https://docs.flutter.dev/development/data-and-backend/state-mgmt/options&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Periodically, Mike Rydstrom (&lt;a href="https://twitter.com/RydMik" rel="noopener noreferrer"&gt;https://twitter.com/RydMik&lt;/a&gt;) publishes an overview of the State Management Solutions with the most LIKEs. &lt;a href="https://twitter.com/RydMike/status/1528827017172504579" rel="noopener noreferrer"&gt;https://twitter.com/RydMike/status/1528827017172504579&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the hottest topic in the community. Hardly anyone doesn't have an opinion on it. As far as I understand, there is no official recommendation from Google. There is a discussion about whether there was once an official endorsement for Flutter BLoC. At IO '18, it seems that the BLoC architecture was presented and the package was then developed according to this architecture design. At the moment there seems to be a big momentum for Provider and for the next generation of it: Riverpod. These are maintained by Remi Rousselet (&lt;a href="https://twitter.com/remi_rousselet" rel="noopener noreferrer"&gt;https://twitter.com/remi_rousselet&lt;/a&gt;). Soon there will be a new major release (2.0) of Riverpod and there seems to be a lot of changes and simplifications coming our way in the next few months. See also the talk of Remi at the Flutter Vikings conference &lt;a href="https://youtu.be/C2Zp731g8Es?t=12762" rel="noopener noreferrer"&gt;https://youtu.be/C2Zp731g8Es?t=12762&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The courses of Andrea Bizzotto are great when it comes to Riverpod and a possible architecture. Andrea proposes an architecture and calls it Riverpod Architecture &lt;a href="https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/" rel="noopener noreferrer"&gt;https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get a feel for both architectures, we developed our project with both state management solutions. To be fair, the first version was implemented with Riverpod and the second version with BLoC, so we had already learned a lot during the implementation of the second version. I know NgRx very well from Angular and therefore felt very quickly at home with the BLoC pattern. We also want to store a part of the state in local memory and reload it on restart. We have implemented this with Riverpod itself in the BLoC package there is already a solution &lt;code&gt;hydrated_bloc&lt;/code&gt;. Also the possibility of global observers has helped us a lot in the development and has helped a bit over the lack of Redux tools (in the browser).&lt;/p&gt;

&lt;p&gt;Detailed overview by &lt;a href="https://twitter.com/illiaromanenko" rel="noopener noreferrer"&gt;@illiaromanenko&lt;/a&gt;: &lt;a href="https://github.com/illia-romanenko/flutter_state_management_investigation/blob/main/readme.md" rel="noopener noreferrer"&gt;https://github.com/illia-romanenko/flutter_state_management_investigation/blob/main/readme.md&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  GetX
&lt;/h3&gt;

&lt;p&gt;This seems to be the red rag in the community. In the world of Harry Potter, it would be compared to Voldemort. In the popularity rankings it is high up, if not on the first place when it comes to state management and dependency injection. But as far as I understand it is considered by the community as its own framework and not as an extension for Flutter. It uses completely different approaches and thus hides much of the Flutter DNA.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Injection
&lt;/h3&gt;

&lt;p&gt;As a .NET and Angular developer, I am very familiar with Dependency Injection and use it extensively. So I was rather surprised that there is no solution in the framework out of the box. Again, there are many options and packages. Some even do without Dependency Injection and use singletons or instantiate classes directly. But &lt;code&gt;new is glue&lt;/code&gt; and it makes it difficult to write tests.&lt;/p&gt;

&lt;p&gt;Riverpod already brings a lot with Provider. For the rest and for BLoC we relied on &lt;code&gt;get_it&lt;/code&gt; from Thomas Burkhart (&lt;a href="https://twitter.com/ThomasBurkhartB" rel="noopener noreferrer"&gt;https://twitter.com/ThomasBurkhartB&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Router
&lt;/h3&gt;

&lt;p&gt;Not so long ago Flutter released a new Navigator 2.0. It offers a new declarative API and was received controversially by the community. It is said to be very complicated and hard to learn. (As far as I know you can use Navigator 1.0 together with Navigator 2.0).&lt;/p&gt;

&lt;p&gt;There are several packages that aim to make the usage of the new Navigator 2.0 easier. We have decided on using the &lt;code&gt;go_router&lt;/code&gt; package. There was a new major release just a few days ago: &lt;a href="https://pub.dev/packages/go_router" rel="noopener noreferrer"&gt;https://pub.dev/packages/go_router&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We were missing the guard functionality of the Angular router. As a hint: most of the guard logic can be implemented in the &lt;code&gt;redirect&lt;/code&gt; block of the &lt;code&gt;go_router&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Flutter on the Web
&lt;/h2&gt;

&lt;p&gt;What finally moved us to work with Flutter was the possibility to publish for the web as well. Unfortunately, the implementation is quite "unconventional". Unlike Angular or React, Flutter does not produce HTML and CSS, but uses the canvas. There are two different rendering options with their advantages and disadvantages.&lt;/p&gt;

&lt;p&gt;Some background articles on this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.flutter.dev/development/platform-integration/web/renderers" rel="noopener noreferrer"&gt;https://docs.flutter.dev/development/platform-integration/web/renderers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wilsonwilson.dev/articles/flutter-web-renderers" rel="noopener noreferrer"&gt;https://wilsonwilson.dev/articles/flutter-web-renderers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/flutter/hummingbird-building-flutter-for-the-web-e687c2a023a8" rel="noopener noreferrer"&gt;https://medium.com/flutter/hummingbird-building-flutter-for-the-web-e687c2a023a8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/flutter/flutter-web-support-updates-8b14bfe6a908" rel="noopener noreferrer"&gt;https://medium.com/flutter/flutter-web-support-updates-8b14bfe6a908&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@GSYTech/how-to-optimize-flutter-web-and-how-flutter-web-work-in-html-renderer-b399ffd66718" rel="noopener noreferrer"&gt;https://medium.com/@GSYTech/how-to-optimize-flutter-web-and-how-flutter-web-work-in-html-renderer-b399ffd66718&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing Flutter Web Apps
&lt;/h3&gt;

&lt;p&gt;This is what I missed the most from Angular. I am a big one of integration testing in the frontend. For this I prefer to use Cypress. Cypress is unfortunately not usable with Flutter, resp. only via detours and by far not as comfortable as Angular.&lt;br&gt;
With the &lt;code&gt;integration_test&lt;/code&gt; package similar results can be achieved and hot reload seems to be possible. Unfortunately I failed there. For the current project we have also set the &lt;code&gt;BLoC&lt;/code&gt; package and for a certain part of the state we need the &lt;code&gt;hydrated_bloc&lt;/code&gt; package. Unfortunately at the moment it seems that no integration tests can be realized: &lt;a href="https://github.com/flutter/flutter/issues/96939" rel="noopener noreferrer"&gt;https://github.com/flutter/flutter/issues/96939&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The next comments are strictly my personal opinion and refer only to the web version and not to the mobile development part of Flutter.&lt;/p&gt;

&lt;p&gt;I love Flutter's declarative widget system. The speed at which prototypes and executable MVPs can be developed, for all platforms, is impressive. &lt;/p&gt;

&lt;p&gt;I would not, as of today, implement a complex and long-lived web application with Flutter. For an executable prototype or a small MVP to test the market, Flutter is great.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>angular</category>
    </item>
    <item>
      <title>Publish a localized Angular app with Scully</title>
      <dc:creator>Markus Huggler</dc:creator>
      <pubDate>Tue, 10 Nov 2020 20:50:53 +0000</pubDate>
      <link>https://dev.to/novaloop/publish-a-localized-angular-app-with-scully-3mf2</link>
      <guid>https://dev.to/novaloop/publish-a-localized-angular-app-with-scully-3mf2</guid>
      <description>&lt;h2&gt;
  
  
  Making Scully work with our localized Angular App
&lt;/h2&gt;

&lt;p&gt;In the first part of the series we have localized our Angular App using the official &lt;code&gt;@angular/localize&lt;/code&gt;. Now we need to make Scully work with our setup.&lt;/p&gt;

&lt;p&gt;First of all, install scully, we are using the Scully schematic for this task:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ng add @scullyio/init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--localize&lt;/code&gt; option when building the Angular app does creates builds for each locale and changes the &lt;code&gt;baseHref&lt;/code&gt; of the those &lt;code&gt;index.html&lt;/code&gt; file. Unfortunately this does stop Scully from traversing the routes correctly. To bypass this issue we set the base path back to '/' before building the Scully app. Afterwards we need to set it back and correct the paths of the static assets. Fortunately there are plugins for both latter issues. You can find a discussion on the topic &lt;a href="https://github.com/scullyio/scully/issues/318" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first Plugin is from &lt;a href="https://github.com/SanderElias" rel="noopener noreferrer"&gt;Sander Elias&lt;/a&gt;: &lt;a href="https://github.com/scullyio/scully/tree/master/libs/plugins/base-href-rewrite" rel="noopener noreferrer"&gt;base-href-rewrite&lt;/a&gt; and can be installad via npm &lt;code&gt;npm install -D @scullyio/scully-plugin-base-href-rewrite&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;The second plugin is from &lt;a href="https://github.com/dyingangel666" rel="noopener noreferrer"&gt;Alexander Spies&lt;/a&gt; and you can create it directly in your project. Create a &lt;code&gt;scully/plugins/fixStaticLinks.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const {registerPlugin} = require('@scullyio/scully');

const FixStaticLinksPlugin = 'fixStaticLinks';

const fixStaticLinksPlugin = async (html) =&amp;gt; {
    const regex = new RegExp('(&amp;lt;a[^&amp;gt;]* href="\/)([^"]*)"', 'gmi');
    html = html.replace(regex, `$1${process.env.LOCALE}/$2"`);

    return Promise.resolve(html);
};


registerPlugin('router', 'fixStaticLinks', fixStaticLinksPlugin);
exports.FixStaticLinksPlugin = FixStaticLinksPlugin;

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

&lt;/div&gt;



&lt;p&gt;Then we are ready to adapt the &lt;code&gt;scully.localization.config.ts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {baseHrefRewrite} from '@scullyio/scully-plugin-base-href-rewrite';
import {ScullyConfig, setPluginConfig} from '@scullyio/scully';

const {FixStaticLinks} = require('./scully/plugins/fixStaticLinks');

const defaultPostRenderers = [baseHrefRewrite, FixStaticLinks];
setPluginConfig(baseHrefRewrite, {href: `/${process.env.LOCALE}/`});

export const config: ScullyConfig = {
    projectRoot: './src',
    projectName: 'localization',
    distFolder: `./dist/localization/${process.env.LOCALE}`,
    outDir: `./dist/static/${process.env.LOCALE}`,
    puppeteerLaunchOptions: {args: ['--no-sandbox', '--disable-setuid-sandbox']},
    defaultPostRenderers,
    routes: {}
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have registered both renderer plugins in the config. We have also added the &lt;code&gt;distFolder&lt;/code&gt; and we are using an Environment variable for constructing the file system paths.&lt;/p&gt;

&lt;p&gt;Let's put everything together. Here is a Bash script that replaces the base href of the &lt;code&gt;index.html&lt;/code&gt; files (&lt;code&gt;/de/&lt;/code&gt; -&amp;gt; &lt;code&gt;/&lt;/code&gt;) and then runs Scully for both locales.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

languages=(en de)

for language in "${languages[@]}"
do
    index_file="dist/novaloop-ch/$language/index.html"
    echo "Replace base href for $language in $index_file"
    sed -i -e 's/"\/'$language'\/"/"\/"/g' $index_file
    echo "Build scully for $language"
    LOCALE=$language npm run scully
done

cp htaccess.file dist/static/.htaccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last line is only needed if you publish to an Apache server and want some redirect magic. The htaccess.file reads as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RewriteEngine On

RewriteRule ^de$ /de/home/ [L]
RewriteRule ^en$ /en/home/ [L]
RewriteRule !^[a-z]{2}/ /de%{REQUEST_URI} [L,R=302]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full source code of the sample project can be found on &lt;a href="https://gitlab.com/novaloop-oss/angular/localization" rel="noopener noreferrer"&gt;Gitlab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also a bash script (and a Docker file) for starting and testing the Scully build with an Apache server in Docker.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>scully</category>
    </item>
    <item>
      <title>Localize an Angular App</title>
      <dc:creator>Markus Huggler</dc:creator>
      <pubDate>Mon, 09 Nov 2020 22:25:34 +0000</pubDate>
      <link>https://dev.to/novaloop/localize-the-angular-app-2lpc</link>
      <guid>https://dev.to/novaloop/localize-the-angular-app-2lpc</guid>
      <description>&lt;h1&gt;
  
  
  Localize an Angular app and Publish it with Scully
&lt;/h1&gt;

&lt;p&gt;We want to create a localized Angular App and then publish it with Scully. The first hurdle was the localization of the Angular App. In particular, we want to extract the translations and merge them with the already translated phrases.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;xliffmerge&lt;/code&gt; comes in handy for this. You can install it with the &lt;a href="https://github.com/martinroob/ngx-i18nsupport" rel="noopener noreferrer"&gt;ngx-i18nsupport&lt;/a&gt; package.&lt;/p&gt;

&lt;p&gt;The first part of the series covers the localization of the Angular app.&lt;/p&gt;

&lt;p&gt;There is an &lt;a href="https://gitlab.com/novaloop-oss/angular/localization" rel="noopener noreferrer"&gt;example repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localize the Angular
&lt;/h2&gt;

&lt;p&gt;Localize your Angular app using the offical Angular.io Docs:&lt;br&gt;
&lt;a href="https://angular.io/guide/i18n" rel="noopener noreferrer"&gt;https://angular.io/guide/i18n&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ng add @angular/localize&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Edit the &lt;code&gt;angular.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  ...
  "projects": {
    "&amp;lt;project-name&amp;gt;": {
      "i18n": {
        "sourceLocale": "de",
        "locales": {
          "en": "src/locales/messages.en.xlf"
        }
      }
      "architect": {
        "build": {
          ...
          "configurations": {
            "production": { 
              ... 
              "localize": true,
              ...
            },
            "de": {
              "localize": ["de"]
            },
            "en": {
              "localize": ["en"]
            }
          }
        },
        "serve": {
          ...
          "configurations": {
            ...
            "de": {
              "browserTarget": "&amp;lt;package-name&amp;gt;:build:de"
            },
            "en": {
              "browserTarget": "&amp;lt;package-name&amp;gt;:build:en"
            }
          }
        }
      }
    }
  },
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add German as the default language (&lt;code&gt;messages.xlf&lt;/code&gt;) and English as the second translated language (&lt;code&gt;messages.en.xlf&lt;/code&gt;). Don't forget to add the &lt;code&gt;localize&lt;/code&gt; flag to the production configuration. Otherwise you will have to specify the flag on each build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extract and subsequent merge of messages file
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;npm install ngx-i18nsupport&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add the following scripts to &lt;code&gt;package.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   ...
   "scripts": {
     ...
     "extract-i18n": "ng xi18n &amp;lt;project-name&amp;gt; --output-path src/locales &amp;amp;&amp;amp; xliffmerge",
     "xliffmerge": "./node_modules/ngx-i18nsupport/dist/xliffmerge"
   },
   "xliffmergeOptions": {
        "srcDir": "src/locales",
        "genDir": "src/locales",
        "defaultLanguage": "de",
        "languages": [
            "en"
        ]
    },
   ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extract and translate the strings
&lt;/h2&gt;

&lt;p&gt;You can now extract the strings with &lt;code&gt;npm run extract-i18n&lt;/code&gt;.&lt;br&gt;
It will generate two files using the above configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/locales/messages.xlf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/locales/messages.en.xlf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are using &lt;a href="https://poedit.net/" rel="noopener noreferrer"&gt;poedit&lt;/a&gt; to edit our strings and therefore added a npm script:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"translate-en": "poedit src/locales/messages.en.xlf",&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the Angular App in English
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ng serve --configuration=en&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the Angular App
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ng build --prod&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Builds the app for both languages &lt;code&gt;dist/&amp;lt;project-name&amp;gt;/&amp;lt;language&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;We want to use Scully to build the website statically for both languages. It was not easy but I think we did it. This will be the next part of the series.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>scully</category>
    </item>
    <item>
      <title>Complex Configuration in Cypress.io</title>
      <dc:creator>Markus Huggler</dc:creator>
      <pubDate>Tue, 14 May 2019 18:15:55 +0000</pubDate>
      <link>https://dev.to/novaloop/complex-configuration-in-cypress-io-bm6</link>
      <guid>https://dev.to/novaloop/complex-configuration-in-cypress-io-bm6</guid>
      <description>&lt;p&gt;In our setup, multiple sites are deployed with the same code base. So it would make sense to reuse a lot of the testing code for all sites. &lt;/p&gt;

&lt;p&gt;To achieve this we decided to reuse the same Docker container and provide the needed configuration variables via the entrypoint script.&lt;/p&gt;

&lt;p&gt;We have a config Folder in the cypress Folder and are building the final configuration file together from all the pieces.&lt;/p&gt;

&lt;p&gt;To do so we use the &lt;code&gt;index.js&lt;/code&gt; file in the pluginsfolder:&lt;br&gt;
(we also installed the merge-json npm package)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const fs = require('fs-extra');
const mergeJSON = require('merge-json');
const path = require('path');


async function getConfigurationByFile(division, environment, locale) {


    let defaultConfig = {
        "env": {"localePrefix": locale}
    };

    const filePaths = [
        path.resolve(`cypress/config/${division}`, 'defaults.json'),
        path.resolve(`cypress/config/${division}`, 'pages.json'),
        path.resolve(`cypress/config/${division}`, 'seo.json'),
        path.resolve(`cypress/config/${division}`, `env-${environment}.json`)
    ];

    for (let filePath of filePaths) {
        const config = await fs.readJson(filePath);
        if (config.env) {
            Object.keys(config.env).forEach((key) =&amp;gt; {
                config.env[key] = JSON.stringify(config.env[key]);
            });
            defaultConfig = mergeJSON.merge(defaultConfig, config);
        }
    }

    return new Promise((resolve, reject) =&amp;gt; {
        resolve(
            defaultConfig
        );
    })
}

module.exports = (on, config) =&amp;gt; {
    const division = config.env.division;
    const environment = config.env.environment;
    const locale = config.env.locale;

    return getConfigurationByFile(division, environment, locale);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cypress does only allow to store key / value pairs &lt;code&gt;env&lt;/code&gt; of the config. Therefore we apply &lt;code&gt;JSON.stringify()&lt;/code&gt; on them and later on in the spec file we parse them back to JSON objects &lt;code&gt;JSON.parse(Cypress.env('&amp;lt;env_key&amp;gt;'))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We have configured our Cypress.io Test Suite with Gitlab CI/CD. The pipeline can be triggered manually for the production and for the staging environemnt and is run automatically once a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Need more details?
&lt;/h2&gt;

&lt;p&gt;Please let us know if you are interested in more details!&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>typescript</category>
      <category>testing</category>
    </item>
    <item>
      <title>One Docker Image to Rule them All</title>
      <dc:creator>Markus Huggler</dc:creator>
      <pubDate>Tue, 14 May 2019 16:35:18 +0000</pubDate>
      <link>https://dev.to/novaloop/one-docker-image-to-rule-them-all-1ic3</link>
      <guid>https://dev.to/novaloop/one-docker-image-to-rule-them-all-1ic3</guid>
      <description>&lt;p&gt;We have a website with multiple divisions. We want to reuse the same docker image for all the divisions.&lt;/p&gt;

&lt;p&gt;First approach was to load the configuration dynamically from a server (depending on the &lt;code&gt;ENV&lt;/code&gt; variables in the image). This would mean that every client will have to send the request and that the configuration server's uptime is crucial.&lt;/p&gt;

&lt;p&gt;We are now using variables in the &lt;code&gt;environment.ts&lt;/code&gt; file, eg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const environment = {
  ...
  url: 'https://__DOMAIN__',
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then have an &lt;code&gt;entrypoint.sh&lt;/code&gt; script for the docker which can be called with arguments and replaces the strings in the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

DOMAIN="$1"

for filename in dist/**/*.js; do
    if [[ $filename =~ "main" ]]; then
        sed -i -e 's/__DOMAIN__/'$DOMAIN'/g' $filename
    fi
done

...

pm2-runtime start pm2.json --web

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

&lt;/div&gt;



&lt;p&gt;The image is used in a Kubernetes deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
  spec:
    containers:
      - image: &amp;lt;image-repo&amp;gt;
        args: ["www.ecologic.ch"]  
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;envsubst&lt;/code&gt; would also be a possibility. You would then use &lt;code&gt;ENV&lt;/code&gt; variables in the &lt;code&gt;environment&lt;/code&gt;files and use &lt;code&gt;envsubst&lt;/code&gt; in the &lt;code&gt;entrypoint&lt;/code&gt; script. In the kubernetes deployment you would then add &lt;code&gt;env&lt;/code&gt; vars instead of &lt;code&gt;args&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Need more details?
&lt;/h2&gt;

&lt;p&gt;Please let us know if you are interested in more details!&lt;/p&gt;

</description>
      <category>angular</category>
      <category>docker</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
