<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[David Serrano]]></title><description><![CDATA[I'm a mobile developer and an entrepreneur. In this blog you will find articles, tutorials and tricks to design, build and grow your mobile apps.]]></description><link>https://davidserrano.io</link><generator>RSS for Node</generator><lastBuildDate>Sun, 19 Apr 2026 17:29:44 GMT</lastBuildDate><atom:link href="https://davidserrano.io/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[My (Dark) Prediction for Android]]></title><description><![CDATA[This article is a more detailed follow-up to a Reddit post I recently published called “My prediction for Android”, which reflects my personal views as an Android developer, based on publicly available information and my own interpretation of current...]]></description><link>https://davidserrano.io/my-dark-prediction-for-android</link><guid isPermaLink="true">https://davidserrano.io/my-dark-prediction-for-android</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[Google]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Sun, 31 Aug 2025 10:36:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756636240068/eb4ba707-4467-42c9-b277-c8e22f11e82f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article is a more detailed follow-up to a Reddit post I recently published called <a target="_blank" href="https://www.reddit.com/r/degoogle/comments/1n25vvn/my_prediction_for_android/">“My prediction for Android”</a>, which reflects my personal views as an Android developer, based on publicly available information and my own interpretation of current trends.</p>
<p>As most of you probably know by now, Google has recently <a target="_blank" href="https://developer.android.com/developer-verification">announced</a> that any Android app will need to be verified by Google before it can be installed on a certified Android device, even if the installation happens outside Google Play.</p>
<p>I have been developing Android applications for more than a decade, and I have witnessed firsthand all the <em>“small”</em> changes Google has rolled out over the years. In my view, these changes are slowly but surely destroying the idea of a truly open Android as we once knew it.</p>
<p>Beyond this concerning requirement, we are starting to see evidence that <strong>bootloaders may no longer be unlockable in the future</strong>. On top of that, <strong>Google has stopped releasing the device trees for the Pixel 10 series devices</strong>, which makes it much harder for custom ROMs to be ported to these devices. And then there is the <strong>growing push for Play Integrity</strong>, a technology that essentially forces you to run an app on a certified device with Play Services installed. When you put all these pieces together, the picture becomes very, very concerning.</p>
<h2 id="heading-the-recent-controversy">The recent controversy</h2>
<blockquote>
<p>By making Android safer, we're protecting the open environment that allows developers and users to confidently create and connect. Android's new developer verification is an extra layer of security that deters bad actors and makes it harder for them to spread harm.</p>
<p>Starting in September 2026, Android will require all apps to be registered by verified developers in order to be installed on certified Android devices.</p>
</blockquote>
<p>This is how Google introduced its <a target="_blank" href="https://developer.android.com/developer-verification">new policy</a> to every Android developer, regardless of whether they publish on the Play Store or not. From that moment on, <strong>if you want to install an app on a certified Android device, it will first have to pass Google’s verification process</strong>. And let me be clear here: that’s the overwhelming majority of Android devices in existence.</p>
<p>The verification process itself is very similar to what already exists inside Google Play. Developers basically have to provide identity documents and personal information so that Google can confirm who you are as an individual, or verify your legal identity if you are representing a company.</p>
<p>Google says this measure is <em>“for user safety”</em>. And while I do think that on some level this can help reduce the distribution of malware on Android devices, I think there’s more to this story than what we’re being told. </p>
<p>First, let’s clarify what a <em>“certified Android device”</em> actually is: it’s any version of Android that has been authorized by Google, where Google Play Services and the Play Store are pre-installed. In other words, almost every phone and tablet you can buy. You can see the list of certified brands <a target="_blank" href="https://www.android.com/certified/partners/">here</a>.</p>
<p>So yes, I can see how this might reduce the spread of malicious apps, since knowing the real identity of a developer can discourage bad actors. But at the same time, this effectively ends the privacy of many legitimate developers who contribute to the Android ecosystem, people who don’t write malware and who simply don’t want to hand over this type of personal information.</p>
<p>It also raises serious concerns about which types of apps may get banned. Think of apps that provide functionality outside of what Google officially allows, like some emulators or similar. This could easily extend to any app that Google <em>—or anyone powerful enough to pressure Google—</em> decides is <em>“unacceptable”</em>. And that takes us into the realm of political and geopolitical pressure, censorship, and ultimately turns Android into a closed ecosystem... almost exactly like Apple’s.</p>
<p>Yes, it’s true that many custom ROMs such as GrapheneOS or CalyxOS would remain outside of this system, meaning that users of these ROMs could still sideload any app they want without Google’s verification. But the real issue is that many developers may choose not to (or may not be allowed to) pass through this verification process. And in doing so, they risk losing exposure to a massive user base, along with the incentive to keep building apps for Android at all.</p>
<p>In other words, <strong>if a developer suddenly loses 99% of their potential audience, what real incentive do they have to keep building apps? This is why I believe the measure could indirectly hurt the entire custom ROM ecosystem as well.</strong></p>
<h2 id="heading-no-more-unlockable-bootloaders">No more unlockable bootloaders?</h2>
<p>This trend could already harm custom ROMs by reducing the amount of software available to run on them, making life harder for their users. But the challenges for custom ROMs do not end there.</p>
<p>For those who may not know, in order to install a custom ROM such as LineageOS or GrapheneOS on an Android device, the bootloader must be unlockable. If the manufacturer decides to disable this option, then that device will only ever be able to run the stock operating system and nothing else. And as I mentioned earlier, some manufacturers, like <a target="_blank" href="https://sammyguru.com/breaking-samsung-removes-bootloader-unlocking-with-one-ui-8/">Samsung, are already moving in this direction</a>.</p>
<p>There are several reasons for this. It is very likely that manufacturers are under pressure from large institutions: banks, streaming services, and content providers with strict licensing requirements, to make sure their devices cannot easily be modified. A phone that allows custom ROMs makes it more difficult to enforce DRM and protect licensed content. There are also commercial motivations: manufacturers do not want to deal with warranty claims from users who bricked their phones while flashing custom software. And of course, there are the strategic interests of controlling the ecosystem itself.</p>
<p>In any case, if this trend continues and more manufacturers ship devices without unlockable bootloaders, custom ROMs could eventually cease to exist.</p>
<h2 id="heading-device-trees-and-components-removed-from-aosp">Device trees and components removed from AOSP</h2>
<p>As if the situation were not already bad enough, there is more. In recent years Google has been steadily removing more and more components from AOSP. AOSP stands for <em>Android Open Source Project</em>, which is the open-source foundation of Android and the basis for all ROMs—including the “certified” ones. What Google has been doing is moving components out of AOSP and into their own proprietary repositories, which forces custom ROM developers to find their own replacements.</p>
<p>More recently, with the launch of the Pixel 10 series, Google <a target="_blank" href="https://9to5google.com/2025/06/12/android-open-source-project-pixel-change/">announced that it would no longer be releasing the device trees for these models</a>. A device tree is essentially the source code that describes the hardware: Android itself, drivers, and other key components that are required to run the operating system in that device. Without it, any ROM that wants to support the Pixel 10 will need to rely on <em>reverse engineering</em>.</p>
<p>To give you an idea of how complex reverse engineering can be: if I hand you a text file with clear instructions, all you need to do is follow them. But if I give you the same file scrambled with a secret code, and I don’t give you that code, you first have to decipher it before you can even start following the instructions. As you can imagine, the task suddenly becomes much, much more difficult.</p>
<h2 id="heading-about-the-adoption-of-play-integrity">About the adoption of Play Integrity</h2>
<p>Finally, I want to talk about <a target="_blank" href="https://developer.android.com/google/play/integrity/overview">Play Integrity</a>. This is a Google Play API that Android developers can currently choose to implement. What it does is basically ensure that your app only runs on a certified Android device with Play Services properly functioning.</p>
<p>Many apps already use this protection. For instance, the official ChatGPT app by OpenAI has it enabled, and it simply won’t run on a device without Play Services.</p>
<p>Right now this technology is optional, and it is up to each developer to decide whether or not to adopt it. But I would not be surprised if, over time, more and more pressure is applied to make adoption widespread, or perhaps even mandatory for all apps distributed through Google Play. To be clear, <strong>this is not the case today, and I am speculating here</strong>. Still, if this scenario were to materialize, it would mean that custom ROMs could no longer run any app that relies on this protection.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As you can see, the outlook is quite grim. The only realistic way I see this being stopped is through regulatory intervention. In Europe, for example, there are already laws like the Digital Services Act and especially the Digital Markets Act, which require so-called “gatekeepers” such as Google to allow the distribution of apps through alternative stores and sideloading. If Google’s verification process were to become too restrictive, it could very well attract the attention of European regulators. </p>
<p>In any case, <strong>I predict a very dark future for Android</strong>. I know my perspective may sound pessimistic, and I sincerely hope the future turns out brighter than what I am imagining. But as of now, I fear that <strong>Android is on the verge of abandoning the very freedom that once defined it</strong>.</p>
]]></content:encoded></item><item><title><![CDATA[🛠️ How to Use Multiple Windows in Flutter Desktop]]></title><description><![CDATA[Introduction
One of Flutter’s key strengths is its ability to run on virtually all major platforms today. And while I often focus on mobile and web examples here on this blog, it’s important not to overlook the fact that Flutter is also a perfectly v...]]></description><link>https://davidserrano.io/how-to-use-multiple-windows-in-flutter-desktop</link><guid isPermaLink="true">https://davidserrano.io/how-to-use-multiple-windows-in-flutter-desktop</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Flutter Examples]]></category><category><![CDATA[desktop]]></category><category><![CDATA[Cross Platform App Development. ]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Tue, 24 Jun 2025 14:08:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750773934723/bdfaf44f-3803-4441-807b-ffe35d6a6a97.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>One of Flutter’s key strengths is its ability to run on virtually all major platforms today. And while I often focus on mobile and web examples here on this blog, it’s important not to overlook the fact that Flutter is also a perfectly viable option for Windows, macOS, and Linux.</p>
<p>In fact, the latest major release, Flutter 3.32, introduced some quality-of-life improvements that are definitely worth exploring. So today, we’re going to take a look at working with multiple windows in Flutter desktop.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/I-7ccZiPx6c">YouTube</a></div>
</div>

<p>This won’t be a traditional tutorial. I’m trying out a more relaxed format where I’ll walk you through small but essential code snippets that show how to work with multiple windows. That said, if you’re looking for a more step-by-step guide, don’t worry, <a target="_blank" href="https://github.com/svprdga/cds_2025_04_multi-window">here you can access the complete source code</a>, so you can run it and study it in detail.</p>
<h2 id="heading-the-desktopmultiwindow-plugin">The desktop_multi_window plugin</h2>
<p>Before we dive into the implementation, let’s take a moment to understand how Flutter currently handles multiple windows on desktop.</p>
<p>Out of the box, Flutter doesn’t offer a fully abstracted or official solution for managing multiple windows. If you want to do it manually, you will need to write platform-specific code: creating new native windows and manually instantiating a second FlutterEngine for each one. It’s not exactly beginner-friendly, and definitely not ideal for cross-platform apps trying to stay within Dart code as much as possible.</p>
<p>That’s where the <a target="_blank" href="https://pub.dev/packages/desktop_multi_window">desktop_multi_window</a> plugin comes in. This package wraps all of that complexity and gives us a clean API for creating and managing windows on Linux, macOS, and Windows, without having to dive into C++, Objective-C, or Windows APIs.</p>
<p>Under the hood, though, each of those windows runs its own separate Flutter engine, and they still rely on native code to communicate with each other. That’s why we use Flutter’s familiar <em>MethodChannel</em> system to pass messages between them. So, throughout the video, you’ll see things like <em>MethodCall</em>, <em>invokeMethod</em>, and similar patterns being used to send data back and forth.</p>
<p>Hopefully, the Flutter team will eventually introduce a more official and streamlined way to handle multi-window setups directly from Dart. But until then, this plugin is quite mature and works well for most practical use cases.</p>
<h2 id="heading-create-a-class-that-represents-a-window">Create a class that represents a window</h2>
<p>The first thing I recommend when working with multiple windows is to create a class that holds the properties related to each individual window. In my sample project, I created a class called <em>WindowInfo</em>, and here’s its basic structure:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WindowInfo</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> id;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> name;
  <span class="hljs-keyword">final</span> WindowController controller;

  WindowInfo({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.id,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.name,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.controller,
  });
}
</code></pre>
<p>At the very least, you’ll want to store the window’s id and a <em>WindowController</em>, which will let you later close it, bring it to focus, or perform any other actions. I also added a name field to make identification easier. Depending on the kind of app you’re building, you can extend this class with any additional data you find useful to better manage your open windows.</p>
<p>In my project, I keep an array of these objects in memory so I can easily work with them later on.</p>
<h2 id="heading-creating-windows">Creating Windows</h2>
<p>Let’s start by looking at how we can create new secondary windows from a main window.</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; _createWindow() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Set window details</span>
      <span class="hljs-keyword">final</span> name = <span class="hljs-string">'Window <span class="hljs-subst">$_windowCounter</span>'</span>;
      <span class="hljs-keyword">final</span> windowConfig = {
        <span class="hljs-string">'name'</span>: name,
      };

      <span class="hljs-comment">// Create the new window</span>
      <span class="hljs-keyword">final</span> windowController = <span class="hljs-keyword">await</span> DesktopMultiWindow.createWindow(
        jsonEncode(windowConfig),
      );

      <span class="hljs-comment">// Configure the window using the controller</span>
      windowController
        ..setFrame(<span class="hljs-keyword">const</span> Offset(<span class="hljs-number">100</span>, <span class="hljs-number">100</span>) &amp; <span class="hljs-keyword">const</span> Size(<span class="hljs-number">800</span>, <span class="hljs-number">600</span>))
        ..setTitle(name)
        ..<span class="hljs-keyword">show</span>();

      <span class="hljs-comment">// Add to our tracking list</span>
      setState(() {
        _windows.add(
          WindowInfo(
            id: windowController.windowId,
            name: name,
            controller: windowController,
          ),
        );
        _windowCounter++;
      });
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-keyword">if</span> (mounted) {
        ScaffoldMessenger.of(
          context,
        ).showSnackBar(SnackBar(content: Text(<span class="hljs-string">'Failed to create window: <span class="hljs-subst">$e</span>'</span>)));
      }
    }
}
</code></pre>
<p>We begin by wrapping the whole operation in a try-catch block. That’s because anything related to window management can potentially fai, so better to be safe.</p>
<p>We’re going to prepare the data for the new window. Since we’re ultimately working with the native layer, <em>DesktopMultiWindow</em> relies heavily on plain JSON strings.</p>
<p>So first, we store the window’s name in a variable, and then we create a map that includes a "name" property.</p>
<p>Then we call <em>DesktopMultiWindow.createWindow()</em>, which launches the new window. This returns a <em>WindowController</em> object, which we can use to interact with the window later on.</p>
<p>Next, we configure some properties, position, size, title, and finally call <em>show()</em> to display it.</p>
<p>The final part of the method stores the window’s data in an array, so we can interact with it later. In my example, I’m using <em>setState()</em> just to keep things simple, but I recommend using your favorite state management solution in a real-world project.</p>
<h2 id="heading-closing-windows">Closing windows</h2>
<p>Now let’s take a look at how we can close a secondary window when it’s no longer needed.</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; _closeWindow(<span class="hljs-built_in">int</span> windowId) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Find the window controller for this window ID</span>
      <span class="hljs-keyword">final</span> windowInfo = _windows.firstWhere((w) =&gt; w.id == windowId);

      <span class="hljs-comment">// Use the controller's close method</span>
      <span class="hljs-keyword">await</span> windowInfo.controller.close();

      setState(() {
        _windows.removeWhere((w) =&gt; w.id == windowId);
      });
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-keyword">if</span> (mounted) {
        ScaffoldMessenger.of(
          context,
        ).showSnackBar(SnackBar(content: Text(<span class="hljs-string">'Failed to close window: <span class="hljs-subst">$e</span>'</span>)));
      }
    }
}
</code></pre>
<p>We start with a try-catch block, just like we did when creating windows. Then we look for the <em>WindowInfo</em> object that matches the window ID we want to close. This gives us access to the corresponding controller.</p>
<p>Once we have that, we simply call its <em>close()</em> method. That’s all it takes to close the actual native window.</p>
<p>After closing it, we also want to keep our internal list of windows clean, so we remove the corresponding <em>WindowInfo</em> from the array.</p>
<p>And finally, if anything goes wrong during the process, we show a quick message to the user using a SnackBar, or whatever error handling method you prefer.</p>
<h2 id="heading-send-messages-to-a-secondary-window">Send messages to a secondary window</h2>
<p>Let’s take a look at how to send messages from the main window to a secondary one.</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; _sendMessageToWindow(<span class="hljs-built_in">int</span> windowId) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">final</span> response = <span class="hljs-keyword">await</span> DesktopMultiWindow.invokeMethod(
        windowId,
        <span class="hljs-string">'message_from_main'</span>,
        <span class="hljs-string">'Hello from main window!'</span>,
      );

      <span class="hljs-keyword">if</span> (mounted) {
        ScaffoldMessenger.of(
          context,
        ).showSnackBar(SnackBar(content: Text(<span class="hljs-string">'Response: <span class="hljs-subst">$response</span>'</span>)));
      }
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-keyword">if</span> (mounted) {
        ScaffoldMessenger.of(
          context,
        ).showSnackBar(SnackBar(content: Text(<span class="hljs-string">'Failed to send message: <span class="hljs-subst">$e</span>'</span>)));
      }
    }
}
</code></pre>
<p>We start by using <em>DesktopMultiWindow.invokeMethod()</em>, which allows us to send a message directly to another window by its ID.</p>
<p>The first argument is the ID of the target window. The second is the method name, in this case, "message_from_main". And the third is the data we want to send, which is just a string message.</p>
<p>This method returns a response from the secondary window, if any. In this example, we display that response using a <em>SnackBar</em>, but you could use it however you like.</p>
<p>To receive messages in the secondary window, we first register a method handler during the <em>initState()</em> of our widget:</p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
<span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();

    <span class="hljs-comment">// Listen for messages from the main window</span>
    DesktopMultiWindow.setMethodHandler(_handleMethodCall);
}
</code></pre>
<p>This sets up the method that will handle incoming calls from other windows, typically from the main window.</p>
<p>Now here’s what the actual handler method looks like:</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-built_in">dynamic</span>&gt; _handleMethodCall(MethodCall call, <span class="hljs-built_in">int</span> fromWindowId) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">if</span> (call.method == <span class="hljs-string">'message_from_main'</span>) {
      <span class="hljs-keyword">final</span> message = call.arguments.toString();

      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(message)),
      );
      <span class="hljs-keyword">return</span> <span class="hljs-string">'Message received by secondary window <span class="hljs-subst">${widget.windowId}</span>'</span>;
    }
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
}
</code></pre>
<p>Inside this function, we check if the method is "message_from_main". If it is, we extract the message, display it using a <em>SnackBar</em>, and return a response indicating that the message was received.</p>
<p>This is the response that the main window gets back when it calls <em>invokeMethod()</em>.</p>
<p>This simple setup lets each secondary window listen for commands or data from the main window, and respond if needed. And of course, you can add as many method types as your app requires.</p>
<h2 id="heading-send-messages-to-the-main-window-from-a-secondary-window">Send messages to the main window from a secondary window</h2>
<p>We have already seen how the main window can send messages to secondary ones. Now let’s quickly complete the picture by allowing secondary windows to send messages back to the main window.</p>
<p>This is all done in the same way, using <em>DesktopMultiWindow.invokeMethod()</em>:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">await</span> DesktopMultiWindow.invokeMethod(
  <span class="hljs-number">0</span>, <span class="hljs-comment">// 0 is usually the ID of the main window</span>
  <span class="hljs-string">'message_from_secondary'</span>,
  <span class="hljs-string">'Hello from window <span class="hljs-subst">${widget.windowId}</span>'</span>,
);
</code></pre>
<p>The only difference here is that we’re targeting window ID 0, which represents the main window.</p>
<p>On the main window side, we handle this message with a method handler registered during <em>initState()</em>:</p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
<span class="hljs-keyword">void</span> initState() {
  <span class="hljs-keyword">super</span>.initState();

  <span class="hljs-comment">// Listen for messages from secondary windows</span>
  DesktopMultiWindow.setMethodHandler(_handleMethodCall);
}
</code></pre>
<p>And here’s the handler itself:</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-built_in">dynamic</span>&gt; _handleMethodCall(MethodCall call, <span class="hljs-built_in">int</span> fromWindowId) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">if</span> (call.method == <span class="hljs-string">'message_from_secondary'</span>) {
    <span class="hljs-keyword">final</span> message = call.arguments.toString();

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );

    <span class="hljs-keyword">return</span> <span class="hljs-string">'Message received by main window'</span>;
  }
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
}
</code></pre>
<p>With this, you now have full two-way communication between windows using simple method calls and arguments.</p>
<h2 id="heading-focusing-a-window">Focusing a window</h2>
<p>Let’s wrap up this overview by showing how to bring a specific window to the front.</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; _focusWindow(<span class="hljs-built_in">int</span> windowId) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Find the window controller for this window ID</span>
      <span class="hljs-keyword">final</span> windowInfo = _windows.firstWhere((w) =&gt; w.id == windowId);

      <span class="hljs-comment">// Use the controller's show method to bring window to front</span>
      <span class="hljs-keyword">await</span> windowInfo.controller.<span class="hljs-keyword">show</span>();
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-keyword">if</span> (mounted) {
        ScaffoldMessenger.of(
          context,
        ).showSnackBar(SnackBar(content: Text(<span class="hljs-string">'Failed to focus window: <span class="hljs-subst">$e</span>'</span>)));
      }
    }
}
</code></pre>
<p>First, we search for the <em>WindowInfo</em> object that matches the ID of the window we want to focus. That gives us access to its controller.</p>
<p>Then we simply call <em>show()</em> on the controller. Internally, this brings the window to the foreground if it’s already open. It’s an easy way to programmatically focus or activate any window you’ve previously created.</p>
<p>And that’s all it takes.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Today we explored how to manage multiple windows in a Flutter desktop app using the <a target="_blank" href="https://pub.dev/packages/desktop_multi_window">desktop_multi_window</a> plugin. We looked at how to create and close windows, how to send messages back and forth between them, and how to bring any window into focus when needed.</p>
<p>Hopefully this gave you a solid overview of how multi-window support works in Flutter and how to integrate it into your own projects.</p>
<p>Thank you very much for reading this article to the end. I hope you have a wonderful rest of your day, goodbye.</p>
]]></content:encoded></item><item><title><![CDATA[5 Tips to Optimize Your Flutter App 🔥🚀]]></title><description><![CDATA[Flutter is a fantastic tool for building cross-platform applications quickly and effectively, as it allows you to launch your app for Android, iOS, Windows, macOS, and Linux — all from a single codebase. However, one of the most common criticisms of ...]]></description><link>https://davidserrano.io/5-tips-to-optimize-your-flutter-app</link><guid isPermaLink="true">https://davidserrano.io/5-tips-to-optimize-your-flutter-app</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Flutter Examples]]></category><category><![CDATA[optimization]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[app development]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Wed, 23 Apr 2025 16:31:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746718496543/62bdfa09-032f-4103-8ec8-9345a43fec23.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Flutter is a fantastic tool for building cross-platform applications quickly and effectively, as it allows you to launch your app for Android, iOS, Windows, macOS, and Linux — all from a single codebase. However, one of the most common criticisms of Flutter throughout its history has been its performance, especially on certain platforms like Apple’s devices. That’s why it’s crucial for you, as a developer, to have the right skills to optimize your app as much as possible, ensuring it runs fast and smoothly on the user’s device. Ideally, the user shouldn’t even be able to tell the difference between a native app and one built with Flutter.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/qSXfSPV4yDw">YouTube</a></div>
</div>

<p>I’ve been working with Flutter since the framework was still in its beta phase, and over the years, I’ve picked up quite a few tricks that help me achieve this goal. In this article, I want to share with you the five most important aspects I believe you should keep in mind when it comes to optimizing your Flutter application.</p>
<p>So without further ado, let’s dive in!</p>
<h2 id="heading-avoid-creating-methods-that-return-widgets">Avoid creating methods that return Widgets</h2>
<p>One of the most common mistakes I see among Flutter developers —especially those who come from other frameworks or are just getting started— is creating methods that return widgets. While this might seem like a harmless shortcut to keep your code organized, it actually has a direct negative impact on your app’s performance.</p>
<pre><code class="lang-dart">Text createTextWidget() {
  <span class="hljs-keyword">return</span> Text(<span class="hljs-string">'Widget created inside a method.'</span>);
}
</code></pre>
<p>The reason is simple: every time Flutter’s build method runs and hits that method, it will create a brand-new widget instance. Even if nothing has changed, Flutter has no way of knowing that the widget is the same, because you’re giving it a freshly created object every single time. As a result, you lose Flutter’s ability to optimize the widget tree and avoid unnecessary rebuilds.</p>
<p>Let’s take a look at an example of this problem:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  State&lt;ExampleWidget&gt; createState() =&gt; _ExampleWidgetState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ExampleWidgetState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ExampleWidget</span>&gt; </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: ListView(
        children: [
          <span class="hljs-keyword">for</span>(<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">100</span>; i++)
            _buildListItem(<span class="hljs-string">'Item <span class="hljs-subst">$i</span>'</span>),
        ],
      ),
    );
  }

  Widget _buildListItem(<span class="hljs-built_in">String</span> title) {
    <span class="hljs-keyword">return</span> ListTile(
      title: Text(title),
    );
  }
}
</code></pre>
<p>At first glance, this code looks clean and functional. However, every time Flutter rebuilds <em>ExampleWidget</em>, it will call <em>_buildListItem()</em> for every single item in the list, generating new widget instances even if the list hasn’t changed at all.</p>
<blockquote>
<p>If your app refreshes this layout 60 times per second, you might think that Flutter would need to build 100 widgets for each refresh — that is, a total of 6,000 builds. However, Flutter applies plenty of optimizations under the hood. In this example, each list item ends up being built around 300 times.</p>
</blockquote>
<p>The proper solution: <strong>extract the widget into a dedicated class.</strong></p>
<p>Instead of building the list items through a method, it’s better to define a separate widget class for each item. This allows Flutter to compare widget instances properly and optimize rebuilds.</p>
<p>Let’s refactor the previous example:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  State&lt;ExampleWidget&gt; createState() =&gt; _ExampleWidgetState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ExampleWidgetState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ExampleWidget</span>&gt; </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: ListView(
        children: [
          <span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">100</span>; i++)
            _ListItem(title: <span class="hljs-string">'Item <span class="hljs-subst">$i</span>'</span>),
        ],
      ),
    );
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ListItem</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> title;

  <span class="hljs-keyword">const</span> _ListItem({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.title});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> ListTile(title: Text(title));
  }
}
</code></pre>
<p>Now, Flutter can treat each <em>_ListItem</em> as an independent widget. If the parent rebuilds, Flutter won’t recreate every list item unnecessarily — it will only update what’s needed.</p>
<blockquote>
<p>If we go back to how many times Flutter builds each widget in the list, previously, Flutter was building each one around 300 times. Now, thanks to this small change, Flutter only needs to build the items in the list about 57 times. It’s also worth noting that other optimizations are happening under the hood here, especially with the ListView widget.</p>
</blockquote>
<p>👉 As a rule of thumb, <strong>you should almost never create widgets inside methods</strong>. Instead, extract them into their own classes, even private ones within the same file; to help Flutter do its job efficiently. Your widget tree will be cleaner, your app will perform better, and you’ll make life easier for both yourself and the framework.</p>
<h2 id="heading-prefer-using-listviewbuilder-over-passing-a-list-of-widgets">Prefer using ListView.builder() over passing a list of widgets</h2>
<p>When rendering lists in Flutter, a common approach is to pass an array of widgets directly to the children parameter of ListView. While this works fine for small lists, it quickly becomes a problem as your list grows.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">return</span> Scaffold(
  body: ListView(
    children: [
      <span class="hljs-comment">// Long list of widgets...</span>
    ],
  ),
);
</code></pre>
<p>That’s because Flutter will instantiate all those widgets in memory immediately, even if most of them aren’t visible. This increases memory usage and slows down your initial build.</p>
<p>It’s true that Flutter won’t call the build() method for every widget right away — it only builds what’s visible plus a small buffer for smooth scrolling. However, since all widget instances already exist in memory, you’re still paying the cost upfront.</p>
<p>Let’s take a look at an example of this problem:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WorkingWithListsWrong</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt; items = <span class="hljs-built_in">List</span>.generate(<span class="hljs-number">10000</span>, (index) =&gt; <span class="hljs-string">'Item <span class="hljs-subst">$index</span>'</span>);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: ListView(
        children: [
          <span class="hljs-keyword">for</span> (<span class="hljs-keyword">final</span> item <span class="hljs-keyword">in</span> items)
            ListTile(title: Text(item)),
        ],
      ),
    );
  }
}
</code></pre>
<p>In this example, we’re generating 10,000 items and passing them directly to ListView. Flutter will try to instance all 10,000 widgets immediately, even though the user will only see a small handful of them at any given time. This is a recipe for performance issues, especially on lower-end devices.</p>
<p>The proper solution: <strong>use ListView.builder()</strong></p>
<p>To avoid this problem, simply use the builder constructor. Flutter will only create the widgets as they’re needed, keeping memory usage low and scroll performance smooth.</p>
<p>Here’s how you can refactor the previous example:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt; items = <span class="hljs-built_in">List</span>.generate(<span class="hljs-number">10000</span>, (index) =&gt; <span class="hljs-string">'Item <span class="hljs-subst">$index</span>'</span>);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          <span class="hljs-keyword">return</span> ListTile(title: Text(items[index]));
        },
      ),
    );
  }
}
</code></pre>
<p>Now, instead of instantiating 10,000 widgets up front, Flutter will only instantiate the ones that are currently visible. This simple change massively improves performance, especially with large lists.</p>
<p>👉 As a rule of thumb, whenever you work with lists of dynamic or large data sets, <strong>you should always prefer ListView.builder()</strong> over passing an explicit list of widgets. It’s a small change that has a big impact, keeping your app fast and responsive, even as your data grows.</p>
<h2 id="heading-avoid-heavy-logic-and-repeated-calculations-inside-build">Avoid heavy logic and repeated calculations inside build()</h2>
<p>One of the best ways to keep your Flutter app fast and responsive is to keep the <em>build()</em> method as light as possible. Remember: <em>build()</em> can be called many times during the lifecycle of your widget — much more often than you might expect. If you place heavy operations inside build(), you’re forcing Flutter to repeat that work over and over again.</p>
<p>But it doesn’t stop there. Even lightweight operations can become problematic if you repeat them unnecessarily inside the same <em>build()</em> method. Flutter executes everything inside build() line by line, so if you call the same calculation multiple times, it will recompute it every single time.</p>
<p>Let’s look at an example of this problem:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  State&lt;ExampleWidget&gt; createState() =&gt; _ExampleWidgetState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ExampleWidgetState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ExampleWidget</span>&gt; </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">int</span>&gt; numbers = <span class="hljs-built_in">List</span>.generate(<span class="hljs-number">10000</span>, (index) =&gt; index);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Column(
      children: [
        Text(<span class="hljs-string">'Max number: <span class="hljs-subst">${_findMax()}</span>'</span>),
        Text(<span class="hljs-string">'Is 5000 in the list? <span class="hljs-subst">${numbers.contains(<span class="hljs-number">5000</span>)}</span>'</span>),
        Text(<span class="hljs-string">'Max number again: <span class="hljs-subst">${_findMax()}</span>'</span>),
      ],
    );
  }

  <span class="hljs-built_in">int</span> _findMax() {
    <span class="hljs-keyword">return</span> numbers.reduce((a, b) =&gt; a &gt; b ? a : b);
  }
}
</code></pre>
<p>In this example:</p>
<ul>
<li><p><em>_findMax()</em> is called twice in the same build(), causing the calculation to run two times unnecessarily.</p>
</li>
<li><p><em>numbers.contains(5000)</em> is an expensive operation for large lists.</p>
</li>
<li><p>Every time the widget rebuilds, these calculations will happen again.</p>
</li>
</ul>
<p>The proper solution: <strong>compute once and reuse</strong></p>
<p>The solution is simple: compute your values once, store them in local variables, and reuse them throughout <em>build()</em>.</p>
<p>Here’s the improved version:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  State&lt;ExampleWidget&gt; createState() =&gt; _ExampleWidgetState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ExampleWidgetState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ExampleWidget</span>&gt; </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">int</span>&gt; numbers = <span class="hljs-built_in">List</span>.generate(<span class="hljs-number">10000</span>, (index) =&gt; index);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> maxNumber = _findMax();
    <span class="hljs-keyword">final</span> contains5000 = numbers.contains(<span class="hljs-number">5000</span>);

    <span class="hljs-keyword">return</span> Column(
      children: [
        Text(<span class="hljs-string">'Max number: <span class="hljs-subst">$maxNumber</span>'</span>),
        Text(<span class="hljs-string">'Is 5000 in the list? <span class="hljs-subst">$contains5000</span>'</span>),
        Text(<span class="hljs-string">'Max number again: <span class="hljs-subst">$maxNumber</span>'</span>),
      ],
    );
  }

  <span class="hljs-built_in">int</span> _findMax() {
    <span class="hljs-keyword">return</span> numbers.reduce((a, b) =&gt; a &gt; b ? a : b);
  }
}
</code></pre>
<p>Now, even if <em>build()</em> is called multiple times, each expensive operation only runs once per build. This keeps your UI fast and avoids redundant work.</p>
<p>But, we can do it even better: move calculations to <em>initState()</em>:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  State&lt;ExampleWidget&gt; createState() =&gt; _ExampleWidgetState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ExampleWidgetState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ExampleWidget</span>&gt; </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">int</span>&gt; numbers = <span class="hljs-built_in">List</span>.generate(<span class="hljs-number">10000</span>, (index) =&gt; index);
  <span class="hljs-keyword">late</span> <span class="hljs-built_in">int</span> maxNumber;
  <span class="hljs-keyword">late</span> <span class="hljs-built_in">bool</span> contains5000;

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    maxNumber = _findMax();
    contains5000 = numbers.contains(<span class="hljs-number">5000</span>);
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Column(
      children: [
        Text(<span class="hljs-string">'Max number: <span class="hljs-subst">$maxNumber</span>'</span>),
        Text(<span class="hljs-string">'Is 5000 in the list? <span class="hljs-subst">$contains5000</span>'</span>),
        Text(<span class="hljs-string">'Max number again: <span class="hljs-subst">$maxNumber</span>'</span>),
      ],
    );
  }

  <span class="hljs-built_in">int</span> _findMax() {
    <span class="hljs-keyword">return</span> numbers.reduce((a, b) =&gt; a &gt; b ? a : b);
  }
}
</code></pre>
<p>With this approach, the heavy calculations happen only once during the widget’s lifecycle. Even if the widget rebuilds multiple times (for example, because of unrelated UI updates), the values are already stored and reused, keeping your build() method clean and extremely fast.</p>
<p>👉 As a rule of thumb, <strong>avoid placing heavy logic inside build()</strong>, and always compute your values once per build, or even better, once per widget lifecycle (if possible).</p>
<h2 id="heading-use-isolates-for-heavy-computations">Use Isolates for heavy computations</h2>
<p>Flutter runs your app’s code in a single thread called the main isolate. This thread handles both your app’s logic and its UI rendering. If you perform heavy computations on the main isolate, such as image processing, large data parsing, or complex mathematical calculations, you risk blocking the UI thread, leading to janky animations, delayed interactions, or even complete freezes.</p>
<p>To avoid this, you can offload heavy tasks to a separate <em>Isolate</em>. Flutter provides this mechanism specifically to help you run CPU-intensive work in parallel without impacting the smoothness of your app.</p>
<p>Imagine you’re applying a filter to an image inside your build() or right after loading it on the main thread:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  State&lt;ExampleWidget&gt; createState() =&gt; _ExampleWidgetState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ExampleWidgetState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ExampleWidget</span>&gt; </span>{
  <span class="hljs-keyword">late</span> Image filteredImage;

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    _applyFilter();
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; _applyFilter() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> image = <span class="hljs-keyword">await</span> loadImage(); <span class="hljs-comment">// Simulated image loading</span>
    <span class="hljs-keyword">final</span> result = applyHeavyFilter(image); <span class="hljs-comment">// Heavy operation on main thread</span>
    setState(() {
      filteredImage = result;
    });
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: filteredImage != <span class="hljs-keyword">null</span>
          ? filteredImage
          : <span class="hljs-keyword">const</span> Center(child: CircularProgressIndicator()),
    );
  }

  <span class="hljs-comment">// Simulated functions</span>
  Future&lt;Image&gt; loadImage() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> Future.delayed(<span class="hljs-keyword">const</span> <span class="hljs-built_in">Duration</span>(milliseconds: <span class="hljs-number">100</span>));
    <span class="hljs-keyword">return</span> Image.asset(<span class="hljs-string">'assets/image.png'</span>);
  }

  Image applyHeavyFilter(Image image) {
    <span class="hljs-keyword">final</span> now = <span class="hljs-built_in">DateTime</span>.now();
    <span class="hljs-keyword">while</span> (<span class="hljs-built_in">DateTime</span>.now().difference(now).inMilliseconds &lt; <span class="hljs-number">2000</span>) {
      <span class="hljs-comment">// Simulate heavy work</span>
    }
    <span class="hljs-keyword">return</span> image;
  }
}
</code></pre>
<p>In this example, <em>applyHeavyFilter()</em> simulates a heavy synchronous operation running on the main isolate. During this time, the app’s UI is blocked, and the user cannot interact with it.</p>
<p>The proper solution: <strong>use an Isolate</strong></p>
<p>Let’s improve this by moving the heavy operation to a separate isolate. This way, the UI remains smooth while the processing happens in the background:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  State&lt;ExampleWidget&gt; createState() =&gt; _ExampleWidgetState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ExampleWidgetState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ExampleWidget</span>&gt; </span>{
  Image? filteredImage;

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    _applyFilter();
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; _applyFilter() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> result = <span class="hljs-keyword">await</span> compute(applyHeavyFilter, <span class="hljs-string">'assets/image.png'</span>);
    setState(() {
      filteredImage = result;
    });
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: filteredImage != <span class="hljs-keyword">null</span>
          ? filteredImage!
          : <span class="hljs-keyword">const</span> Center(child: CircularProgressIndicator()),
    );
  }
}

<span class="hljs-comment">// Top-level function for compute</span>
Image applyHeavyFilter(<span class="hljs-built_in">String</span> assetPath) {
  <span class="hljs-keyword">final</span> now = <span class="hljs-built_in">DateTime</span>.now();
  <span class="hljs-keyword">while</span> (<span class="hljs-built_in">DateTime</span>.now().difference(now).inMilliseconds &lt; <span class="hljs-number">2000</span>) {
    <span class="hljs-comment">// Simulate heavy work</span>
  }
  <span class="hljs-keyword">return</span> Image.asset(assetPath);
}
</code></pre>
<p>By using the <em>compute()</em> function, you offload the heavy task to a background isolate. Your main UI thread stays free to handle animations and user interactions.</p>
<p>👉 When your app needs to handle heavy operations like image processing, data parsing, or complex calculations, <strong>offload that work to an isolate</strong>. It keeps your app responsive and smooth, providing a much better user experience.</p>
<h2 id="heading-optimize-your-images-for-performance">Optimize your images for performance</h2>
<p>Images are often one of the biggest factors affecting your app’s performance. Poorly optimized images can lead to long loading times, high memory usage, and even frame drops if they’re not handled carefully.</p>
<p>There are three main things to consider when using images in your Flutter app:</p>
<ol>
<li><h3 id="heading-use-multiple-resolutions-of-your-images">Use multiple resolutions of your images</h3>
</li>
</ol>
<p>Flutter supports 1x, 2x, and 3x image variants. This allows you to provide lower-resolution images for low-density screens and higher-resolution images for high-density screens. Flutter automatically selects the correct image based on the device’s pixel density.</p>
<p>If you don’t provide these variants, Flutter will scale your image, but it might load unnecessarily large images on devices that don’t need them, wasting memory and processing power.</p>
<p>For example, if you only include a large image in your assets like this:</p>
<p><code>Image.asset('assets/images/large_image.png')</code></p>
<p>And your image is 3000x3000 pixels, even devices that display it at much smaller sizes will load the full-resolution asset. This increases memory usage and slows down your app.</p>
<p>The correct approach is to provide proper resolution variants following Flutter’s conventions:</p>
<pre><code class="lang-plaintext">assets/images/large_image.png       // 1x — 300x300 px
assets/images/2.0x/large_image.png  // 2x — 600x600 px
assets/images/3.0x/large_image.png  // 3x — 900x900 px
</code></pre>
<p>Flutter will automatically pick the right image based on the device’s screen density, giving you better performance without sacrificing image quality.</p>
<ol start="2">
<li><h3 id="heading-use-the-right-image-format">Use the right image format</h3>
</li>
</ol>
<p>Different formats serve different needs:</p>
<ul>
<li><p>PNG for images requiring transparency or sharp edges (like icons).</p>
</li>
<li><p>JPEG for photos and complex images with lots of colors.</p>
</li>
<li><p>WebP for modern, highly compressed images that support both transparency and photos.</p>
</li>
</ul>
<p>Choosing the right format ensures a good balance between quality and file size.</p>
<ol start="3">
<li><h3 id="heading-compress-and-size-your-images-properly">Compress and size your images properly</h3>
</li>
</ol>
<p>No matter the format, always compress your images and use the correct resolution. Avoid using images straight from design tools or cameras at full resolution. For example, if your design only displays an image at 300x300 pixels, there’s no need to load a 3000x3000 pixel asset. Oversized images consume unnecessary memory and slow down your app.</p>
<p>👉 Images are a critical part of your app’s performance. By providing <strong>proper resolution variants, choosing the right formats, and compressing your assets</strong>, you’ll reduce your app’s memory usage and improve loading times, all while keeping your UI crisp and fast on every device.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>And that’s my top 5 tips to help you improve your Flutter app’s performance. If you found this article helpful, feel free to check out <a target="_blank" href="https://www.youtube.com/@DavidSerranoIO">my YouTube channel</a> as well, where I talk about Flutter development, new versions of the framework, and plenty of other topics that you’ll definitely find interesting!</p>
<p>Thank you for reading all the way to the end, I hope you have a beautiful rest of your day!</p>
]]></content:encoded></item><item><title><![CDATA[Your home address exposed on Google Play 😰]]></title><description><![CDATA[In recent months, Google Play has rolled out a new account verification process that applies to all developers. These policies require developers to provide verified information to continue distributing their apps on the platform. The objective, acco...]]></description><link>https://davidserrano.io/your-home-address-exposed-on-google-play</link><guid isPermaLink="true">https://davidserrano.io/your-home-address-exposed-on-google-play</guid><category><![CDATA[Google]]></category><category><![CDATA[privacy]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[Android]]></category><category><![CDATA[google play]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Mon, 16 Sep 2024 15:25:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726499898599/1f55fd0d-aa82-43da-9ea1-edf6dfc79a96.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In recent months, Google Play has rolled out a new account verification process that applies to all developers. <a target="_blank" href="https://support.google.com/googleplay/android-developer/answer/14986433">These policies</a> require developers to provide verified information to continue distributing their apps on the platform. The objective, according to Google, is to ensure that the apps available on Google Play meet high standards of quality and reduce the risk of malicious software being distributed.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/26rlNTr7SIA">YouTube</a></div>
</div>

<p>While these changes are intended to improve the platform's security, they have caused concerns for some developers, especially independent developers with personal accounts, myself included. The most significant worry is related to the new requirement for personal developers to publicly disclose their home address in their developer profile, which can be seen by anyone. For indie developers and individuals who value their privacy, this is understandably alarming.</p>
<p>In this article, I will try to explain in a simple way what this whole issue is about, what potential problems it implies for small developers, and finally I will try to give some clues on how to try to handle this situation better.</p>
<h2 id="heading-understanding-google-play-verification-process">Understanding Google Play verification process</h2>
<p>The Google Play account verification process is designed to identify developers and make apps within the platform safer and more trustworthy.</p>
<p>For individual developers with a personal account, Google mandates that you verify your identity by submitting documentation such as an official ID and a proof of address. The key challenge lies in the fact that personal developers must use their residential address for this verification. This address is then made publicly available if your app includes in-app purchases, which can be unsettling for many.</p>
<p>Here’s a breakdown of how the process works:</p>
<h4 id="heading-personal-accounts">Personal Accounts:</h4>
<ul>
<li><p><strong>Identity Verification:</strong> You are required to provide a government-issued identification document (e.g., a passport or driver’s license).</p>
</li>
<li><p><strong>Address Verification:</strong> You must submit a document showing your residential address, such as a utility bill, bank statement, or lease agreement. Unfortunately, virtual office addresses or PO boxes are not accepted for personal accounts.</p>
</li>
<li><p><strong>Public Disclosure:</strong> If you offer in-app purchases, both your name and full home address will be visible on your Google Play profile. If not, only your name and country will be shown.</p>
</li>
</ul>
<h4 id="heading-organization-accounts">Organization Accounts:</h4>
<ul>
<li>For developers using organization accounts, the process is slightly different. Businesses can use a registered business address rather than a personal address, providing an extra layer of privacy. This option makes it easier for companies to maintain privacy while complying with Google's policies.</li>
</ul>
<p>In contrast to other platforms like Apple’s App Store, which allows more flexibility with virtual office addresses in some cases, Google Play has adopted a stricter stance. As a result, many personal developers are now in a difficult position, needing to expose sensitive information or explore costly alternatives to comply with the rules.</p>
<h2 id="heading-the-problem-why-personal-developers-are-worried"><strong>The problem: why personal developers are worried</strong></h2>
<p>One of the biggest concerns for developers with personal accounts with this new verification process is the <a target="_blank" href="https://support.google.com/googleplay/android-developer/answer/10841920#developer-information">public disclosure of sensitive information</a>. For developers using a personal account, Google requires them to verify their home address, which, in some cases, is made visible to the public. This can be alarming, as it raises privacy and safety issues, especially for individuals who work from home.</p>
<p>Many developers feel uncomfortable with their home addresses being available online for anyone to see. This is especially problematic for indie developers, freelancers, or those who prefer to keep their personal and professional lives separate. In online forums and developer communities, there have been multiple <a target="_blank" href="https://www.reddit.com/r/androiddev/comments/1ery2et/googles_new_verification_is_a_violation_of_privacy/">discussions about these concerns</a>, with some developers sharing their frustrations about the lack of alternatives, such as using virtual offices or PO boxes.</p>
<p>While organization accounts allow developers to use a business address, personal accounts don’t offer this flexibility. This forces many developers to either disclose their home address or consider other costly solutions, such as forming a business just to meet the verification requirements.</p>
<h2 id="heading-alternatives-to-using-a-home-address"><strong>Alternatives to using a home address</strong></h2>
<p>Based on my own experience and the experience of other developers I have discussed this issue with, it appears that this new personal account verification process does not allow the use of alternative addresses, such as virtual offices or PO boxes. These services allow you to rent a professional address for business correspondence without revealing your home location. However, sending to the review team your contract with one of these services, or any document that proves that it is actually legitimate for you to operate with that address, will cause your document to be rejected due to not being one of the documents accepted in the personal account verification process.</p>
<p>So the question is, is there any alternative?</p>
<p>Unfortunately, if you're using a personal account, the options are limited. Some developers have attempted to redirect utility bills to alternative addresses, but this approach depends on your location and whether it's feasible in your case. Otherwise, forming an organization or business may be the only way to use a business address legally for verification purposes.</p>
<h2 id="heading-converting-to-an-organization-account"><strong>Converting to an Organization account</strong></h2>
<p>Switching to an organization account can provide a key benefit for developers who are concerned about privacy: it allows you to use a business address instead of your home address, which is required for personal accounts. This can help protect your personal information from being publicly visible on your Google Play profile. However, forming a business comes with its own set of challenges. Depending on where you live, the cost of starting a company can be quite high, which may not be practical for indie developers or those working on a small scale.</p>
<p>Additionally, running a business introduces legal responsibilities like handling taxes and managing paperwork, which can add complexity and administrative burdens.</p>
<p>It’s also worth noting that in the near future, a <a target="_blank" href="https://support.google.com/googleplay/android-developer/answer/10788890?visit_id=638620895716432677-4180324833&amp;rd=1">new policy</a> will come into effect: for certain types of services, developers are required to register as an organization. This includes apps related to financial services (e.g., banking, loans, cryptocurrency), health and medical apps, VPN services, and government apps. If your app falls into one of these categories, you must create an organization account to comply with Google’s policies. In these cases, switching to an organization account is not just a matter of privacy but also a regulatory requirement.</p>
<h2 id="heading-practical-workarounds-for-personal-developers"><strong>Practical workarounds for personal developers</strong></h2>
<p>For developers who can’t or don’t want to switch to an organization account, the options for protecting your privacy while complying with Google’s policies are limited but not impossible. Although Google doesn’t currently allow virtual offices or PO boxes for personal accounts, there are still a few workarounds you can try to avoid using your home address.</p>
<p>One potential workaround is to redirect certain types of utility bills or other official documentation to an alternative address. For example, if you have access to a secondary property or a family member’s address that you can legally use, you might be able to switch your billing details to that location. Once you have a utility bill or another acceptable document tied to this alternative address, you can submit it for Google’s verification process.</p>
<p>Another option could be renting a co-working space or shared office that provides you with an address and utility services. In some cases, co-working spaces issue utility statements or receipts that can be used for verification.</p>
<p>These workarounds might not be feasible for everyone, and while they don’t offer the same long-term solution as converting to an organization account, they could provide a solution to meet Google’s requirements while protecting your privacy as a personal developer.</p>
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p>In summary, Google Play’s new verification requirements have introduced challenges for personal developers, particularly around the public disclosure of home addresses. While these policies aim to improve transparency and safety on the platform, they have raised valid privacy concerns. For many indie developers, the options are limited—either comply with the rules as they are or explore costly alternatives like switching to an organization account.</p>
<p>However, practical workarounds, such as using alternative addresses for documentation, may offer some relief for those who are not ready to make the leap to an organization account. Regardless of the approach you choose, it’s important to stay informed and prepared if you still have to start this verification process.</p>
<p>I hope this article has been helpful to you, and I regret that I can't offer more definitive solutions to this issue. I hope that in the future, Google will reconsider its policies to better balance transparency and security with the privacy and well-being of developers.</p>
<p>I hope you have a beautiful rest of your day, goodbye.</p>
]]></content:encoded></item><item><title><![CDATA[Learn Flutter by creating your first Flutter app!]]></title><description><![CDATA[In this article, I'm going to introduce you to your first Flutter app. We will explore the parts of a Flutter project, their roles, and we'll cover some fundamental concepts of state, including the differences between StatelessWidgets and StatefulWid...]]></description><link>https://davidserrano.io/learn-flutter-by-creating-your-first-flutter-app-state-management-stateful-vs-stateless-widgets</link><guid isPermaLink="true">https://davidserrano.io/learn-flutter-by-creating-your-first-flutter-app-state-management-stateful-vs-stateless-widgets</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[basics]]></category><category><![CDATA[State Management ]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Sun, 05 May 2024 16:38:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1714926948275/748fb571-90d9-4601-afc5-3a1d8fdaefea.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, I'm going to introduce you to your first Flutter app. We will explore the parts of a Flutter project, their roles, and we'll cover some fundamental concepts of state, including the differences between StatelessWidgets and StatefulWidgets.</p>
<p>If you haven't installed Flutter yet, here are some step-by-step videos that will guide you through the process of installing Flutter on <a target="_blank" href="https://youtu.be/4pKUdxA49UY">Mac</a>, <a target="_blank" href="https://youtu.be/l2TvAVkCgIE">Windows</a>, and <a target="_blank" href="https://youtu.be/RFSF4t5FQhg">Linux</a>.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/tFbN5_a7_ho">YouTube</a></div>
</div>

<h2 id="heading-creating-a-basic-flutter-app">Creating a basic Flutter app</h2>
<p>Let's start by creating a basic application. You can do it from the menus of your favorite code editor, although I always prefer to do it from the terminal:</p>
<p><code>flutter create flutter_test_app</code></p>
<p>This command will create a basic counting app that, when executed, allows us to increment a numerical value by clicking on a button:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712399916949/d1cd64ef-ad62-495b-b46a-ddfeb5525ef4.png" alt class="image--center mx-auto" /></p>
<p>The goal of this sample code is to give you a first introduction to Flutter. We are going to go little by little to understand this template that is already given to us.</p>
<h3 id="heading-the-pubspecyaml-configuration-file">The pubspec.yaml Configuration File</h3>
<p>The first thing we are going to look at is the pubspec.yaml file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712400771180/4d3f2380-70d9-492a-8c07-ab6d7ea12f1e.png" alt class="image--center mx-auto" /></p>
<p>This file contains essential application metadata, enumerates all dependencies, and includes various configuration settings. If you open it, you will find comments for each section that explain their purpose. However, for clarity, we'll remove these comments to keep the file straightforward and briefly overview each segment:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">flutter_test_app</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">"You can add a description for your project here."</span>
<span class="hljs-attr">publish_to:</span> <span class="hljs-string">'none'</span>
<span class="hljs-attr">version:</span> <span class="hljs-number">1.0</span><span class="hljs-number">.0</span><span class="hljs-string">+1</span>

<span class="hljs-attr">environment:</span>
   <span class="hljs-attr">sdk:</span> <span class="hljs-string">'&gt;=3.2.6 &lt;4.0.0'</span>
</code></pre>
<p>Let's clarify the meaning of each parameter:</p>
<ul>
<li><p><strong>name</strong>: This represents the project's name. It serves as an "internal" identifier and is not the name presented to your users.</p>
</li>
<li><p><strong>description</strong>: This field allows you to provide a brief outline of your project's purpose.</p>
</li>
<li><p><strong>publish_to</strong>: This setting is primarily relevant for package development. Since this article focuses on basic concepts, we'll leave it unchanged.</p>
</li>
<li><p><strong>version</strong>: Here, you can specify your project's version using semantic versioning followed by an additional integer. This practice enables version control directly from this file, which then applies across platform-specific projects. The trailing integer typically corresponds to the <em>versionCode</em> in Android projects.</p>
</li>
<li><p><strong>environment</strong>: This specifies the compatible Dart SDK version range for your application. If in the future you want to use new versions of the Dart SDK you might be interested in modifying it, but for now, we leave it as is.</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-attr">cupertino_icons:</span> <span class="hljs-string">^1.0.2</span>

<span class="hljs-attr">dev_dependencies:</span>
  <span class="hljs-attr">flutter_test:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-attr">flutter_lints:</span> <span class="hljs-string">^2.0.0</span>
</code></pre>
<p>The next part focuses on the application's dependencies, which are categorized into two types: those incorporated into the app's final package (<code>dependencies</code>) and those used during development but not included in the final app package (<code>dev_dependencies</code>). To grasp this distinction, let's examine the <a target="_blank" href="https://pub.dev/packages/flutter_lints">flutter_lints</a> package as an example. This package aids in static code analysis, which is performed locally on your machine. There's no need for it to be part of the final app package distributed to users.</p>
<blockquote>
<p>After adding <code>flutter_lints</code> to your <code>dev_dependencies</code> and running <code>flutter pub get</code>, you can analyze your code according to the lint rules specified by <code>flutter_lints</code> using the command <code>flutter analyze</code>. This command checks your code for issues based on the linting rules defined by the package.</p>
</blockquote>
<p>For finding new packages, Flutter developers often visit <a target="_blank" href="https://pub.dev/">https://pub.dev</a>. Say you want to do a network request to a remote server using the <a target="_blank" href="https://pub.dev/packages/http">http</a> package, you can simply add it to your dependencies and then execute <code>flutter pub get</code> to fetch the package and make it ready to use.</p>
<p>Alternatively, you can use the command <code>flutter pub add http</code> to not only download but also automatically add the <code>http</code> package to your <code>dependencies</code>. To add a package to <code>dev_dependencies</code>, you would use <code>flutter pub add --dev package_name</code>.</p>
<p>Experimenting with these methods can help you determine the most comfortable way to manage packages in your Flutter application.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">flutter:</span>
  <span class="hljs-attr">uses-material-design:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>Towards the end of the file, there's a section labeled "flutter," featuring the <code>uses-material-design: true</code> setting. This particular setting informs Flutter that our application will utilize the <a target="_blank" href="https://m3.material.io/">Material Design style</a>, providing a suite of visual, interaction, and motion design guidelines developed by Google.</p>
<p>As you advance more into Flutter development, you'll encounter a range of additional configurations that can be applied within this file to further personalize themes and other aspects of your app.</p>
<p>Additionally, it's worth mentioning the <code>pubspec.lock</code> file, a crucial component of Flutter projects. This file is automatically generated by Flutter when you run commands like <code>flutter pub get</code> or <code>flutter pub add</code>. Its primary purpose is to record the exact versions of each dependency used in your project at the time these commands are executed. This ensures that your project remains consistent and stable, even if dependencies are updated in the future. By tracking these versions, the <code>pubspec.lock</code> file helps to prevent the "it works on my machine" problem by ensuring that every developer working on the project uses the same versions of dependencies, thus minimizing conflicts and compatibility issues.</p>
<h3 id="heading-platform-specific-projects-in-flutter">Platform-Specific Projects in Flutter</h3>
<p>Within a Flutter project, beyond the <code>pubspec.yaml</code> and <code>pubspec.lock</code> files, you'll notice several directories named after platforms. These directories are <code>android</code>, <code>ios</code>, <code>macos</code>, <code>linux</code>, and <code>windows</code>. These aren't just folders; they're complete native projects for their respective platforms.</p>
<p>Flutter's strength lies in its ability to provide a multi-platform development framework, hiding the complexities of platform-specific implementation details. Yet, the existence of these platform-specific projects is crucial for Flutter to operate seamlessly across different environments.</p>
<p>There are instances when you'll need to dip into native development within these directories. This is often the case when certain functionality can only be achieved with platform-specific code. It's in these scenarios that modifications to the native parts of a project become necessary.</p>
<p>It's important to note that you're not required to maintain all these directories if your app doesn't target all supported platforms. For instance, if your focus is solely on Android and iOS, you can safely remove the <code>macos</code>, <code>linux</code>, and <code>windows</code> directories.</p>
<p>Conversely, should you decide to expand your app's availability to additional platforms not initially included in your project, Flutter simplifies this expansion. Using the <code>flutter create</code> command with the <code>--platforms</code> option, you can add the necessary platform projects. For example, if you start with a project only supporting Android and iOS and later decide to include support for macOS, Linux, and Windows, you can execute <code>flutter create --platforms=macos,linux,windows .</code> This command creates the required directories for the new platforms to be supported.</p>
<h1 id="heading-your-apps-source-code-the-lib-directory">Your app's source code: the lib directory</h1>
<p>We have already seen the main files and directories of a Flutter project, although it is true that I have not listed them all, for now I have described the most relevant ones that you should know from the beginning. Now let's jump to the lib directory, the place where all the Dart code that makes up your application resides.</p>
<p>When you create a new project, Flutter automatically generates the <code>main.dart</code> file within the <code>lib</code> folder. This file contains the source code responsible for the counter app that you see if you run the project. If you enter, you will see some comments that explain each section. As before, we are going to eliminate these comments and we are going to explain each part little by little:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  runApp(<span class="hljs-keyword">const</span> MyApp());
}
</code></pre>
<p>The first line introduces an import of <code>material.dart</code>. This import is essential because, by default, we use Material widgets to construct the user interface.</p>
<p>Following that, we encounter the <code>main()</code> method. Every Dart application, Flutter included, requires an entry point, which is provided by the <code>main()</code> function. Within this function, we call <code>runApp()</code>, enabling the application to launch. We pass it an instance of <code>MyApp</code>, which is the next widget we come across in the file:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> MyApp({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'Flutter Demo'</span>,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: <span class="hljs-keyword">true</span>,
      ),
      home: <span class="hljs-keyword">const</span> MyHomePage(title: <span class="hljs-string">'Flutter Demo Home Page'</span>),
    );
  }
}
</code></pre>
<p>This snippet introduces the <code>MyApp</code> widget, serving as the foundational element of your application. It's at the top of the widget hierarchy, essentially acting as the root from which all other widgets will branch out. Flutter apps are structured as a vast tree of widgets, where each widget may be a parent or a child to others. Here, <code>MyApp</code> stands as the initial node in this interconnected structure.</p>
<p><code>MyApp</code> is defined as a class that extends <code>StatelessWidget</code>. <code>StatelessWidgets</code> are characterized by their lack of internal state—they don't manage any data that changes over time. Consequently, a <code>StatelessWidget</code> does not rebuild itself in response to internal data changes. Further details on this will be provided as we progress.</p>
<p>Every <code>StatelessWidget</code> must implement the <code>Widget build(BuildContext context)</code> method. This method is where the app's user interface is constructed. In this example, we create a <code>MaterialApp</code> widget within this method. <code>MaterialApp</code> facilitates the development of an app following Material Design guidelines, including aspects like the app's title and theme.</p>
<p>The <code>home</code> attribute specifies the widget that will be displayed when the app starts. Here, it's set to <code>MyHomePage</code>, which is the widget that comes next in the file:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MyHomePageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">MyHomePage</span>&gt; </span>{
  <span class="hljs-built_in">int</span> _counter = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">void</span> _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            <span class="hljs-keyword">const</span> Text(
              <span class="hljs-string">'You have pushed the button this many times:'</span>,
            ),
            Text(
              <span class="hljs-string">'<span class="hljs-subst">$_counter</span>'</span>,
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: <span class="hljs-string">'Increment'</span>,
        child: <span class="hljs-keyword">const</span> Icon(Icons.add),
      ),
    );
  }
}
</code></pre>
<p><code>MyHomePage</code> is a widget similar to <code>MyApp</code>, but it inherits from <code>StatefulWidget</code> instead of <code>StatelessWidget</code>. This distinction introduces two related classes: <code>MyHomePage</code> itself, which sets up the widget, and <code>_MyHomePageState</code>, a class that manages the widget's state, extending <code>State</code>.</p>
<blockquote>
<p>The reason <code>_MyHomePageState</code> starts with the underscore symbol (_) is to indicate that this class must be private within this file.</p>
</blockquote>
<p>In the state class, we're obliged to implement the <code>Widget build(BuildContext context)</code> method again. However, this time it's within the state class, where we define a widget tree that composes the counter interface:</p>
<ul>
<li><p>Initially, a <code>Scaffold</code> widget lays out the basic structure of our screen, considering elements like system navigation bars.</p>
</li>
<li><p>An <code>AppBar</code> acts as the top navigation bar, where we specify a title and modify the background color using themes.</p>
</li>
<li><p>The <code>Scaffold</code>'s body comprises a <code>Center</code> widget, which ensures its content is centered on the screen. Inside the <code>Center</code>, we place a <code>Column</code> for vertical arrangement of widgets. This column contains two <code>Text</code> widgets: one displays a static message, and the other shows the dynamic <code>_counter</code> value, styled with the current theme.</p>
</li>
<li><p>The <code>floatingActionButton</code> property of the <code>Scaffold</code> employs a <code>FloatingActionButton</code> widget. Positioned at the bottom right, this button is tasked with increasing the counter each time it's pressed.</p>
</li>
</ul>
<h3 id="heading-understanding-state-management-in-flutter">Understanding State Management in Flutter</h3>
<p>Now that we have seen roughly everything that is in the <code>main.dart</code> file, let's understand in a simple way how Flutter manages the state.</p>
<p>As I said before, the <code>_MyHomePageState</code> class is responsible for managing the state of the <code>MyHomePage</code> widget. In this case, we have an application with a numerical value that increments when a button is pressed. This is the state. Specifically, the variable that is defined at the beginning of the class:</p>
<pre><code class="lang-dart">  <span class="hljs-built_in">int</span> _counter = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">void</span> _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
</code></pre>
<p>When we invoke the <code>_incrementCounter()</code> method, it calls the <code>setState</code> method and within it the value of the <code>_counter</code> variable is incremented. By calling the <code>setState</code> method we are updating the state, telling Flutter that it has changed and causing the <code>build</code> method to run again, but this time with the updated state. Later, in the second Widget of type <code>Text</code>, the <code>_counter</code> variable is read to display the value on the screen.</p>
<p>This is a very simplified explanation of state management in Flutter, let's perform an experiment, add the following line just before build returns the <code>Scaffold</code>:</p>
<pre><code class="lang-dart">  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {

    <span class="hljs-built_in">print</span>(<span class="hljs-string">'State refresh'</span>); <span class="hljs-comment">// &lt;-- Add this new line</span>

    <span class="hljs-keyword">return</span> Scaffold(
</code></pre>
<p>Now run the application again and look at the output log. You will see the String "State refresh" printed when you run the application, but also when you modify the value of the counter.</p>
<p>As you can see, this is the simplest way to manage the state of your application in Flutter. In addition, you have also been able to observe the role of the build method and its importance when composing the interface based on state changes.</p>
<h2 id="heading-your-first-app-in-flutter-key-concepts">Your first app in Flutter: key concepts</h2>
<p>If this is your first contact with Flutter, it is possible that at this point you are a little saturated with so much information, don't worry, I am going to list the key points that we have seen throughout the article.</p>
<ul>
<li><p>A Flutter project contains a <code>pubspec.yaml</code> file. It defines several configuration parameters as well as the list of packages that are used by the project.</p>
</li>
<li><p>A Flutter project can contain several subdirectories named after a platform such as <code>android</code> or <code>ios</code>, these are the native projects that Flutter uses to run on each platform.</p>
</li>
<li><p>Inside the <code>lib</code> directory we find the source code of the project, this is where we will write our files in <em>Dart</em> code to create our application.</p>
</li>
<li><p>At its core, a Flutter app is structured as an extensive hierarchy of widgets. Widgets can be of two types: <code>StatefulWidget</code>, which holds state (variables that can change over time), and <code>StatelessWidget</code>, which does not hold state.</p>
</li>
<li><p>We can alter the state of a <code>StatefulWidget</code> by using the <code>setState</code> method.</p>
</li>
</ul>
<p>These are broadly the main basic points that you should know if you are getting started in developing applications with Flutter.</p>
<p>I hope this article has been useful to you. Do not hesitate to follow this blog and my <a target="_blank" href="https://www.youtube.com/@DavidSerranoIO">YouTube channel</a> if you want to continue learning about application development with Flutter, as well as stay up to date with news and other topics of interest about this magnificent framework.</p>
<p>Thanks for reading this far, happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Is it worth learning Flutter in 2024?]]></title><description><![CDATA[Flutter, the great cross-platform framework that allows you to create applications for mobile, web, and desktop with a single codebase. However, despite all the benefits it conveys, is it a good decision to invest time and energy in learning this tec...]]></description><link>https://davidserrano.io/is-it-worth-learning-flutter-in-2024</link><guid isPermaLink="true">https://davidserrano.io/is-it-worth-learning-flutter-in-2024</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[Cross Platform App Development. ]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Fri, 19 Jan 2024 16:01:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705679820916/52c51520-921a-4c7d-bc32-41dc02f7eac4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Flutter, the great cross-platform framework that allows you to create applications for mobile, web, and desktop with a single codebase. However, despite all the benefits it conveys, is it a good decision to invest time and energy in learning this technology in 2024?</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/d173ldnXLzo">YouTube</a></div>
</div>

<p>If you have asked yourself this question lately, it is because in some way you are interested in Flutter, either for the mere fact of learning, because perhaps you are evaluating it for a possible project, or perhaps because you wonder if you could make a living as a Flutter developer working for companies.</p>
<p>My name is David, I have been a Flutter programmer since this framework was in beta, and in this channel, I teach and inform on everything related to Flutter development.</p>
<p>Before we start let me answer the question of whether it is worth learning Flutter in 2024, and the answer is that it depends. It depends a lot on what you are looking for. If you do it to learn new things, or if you are evaluating it for a possible project of yours, then yes, it is worth it. Now, if all you are interested in is finding a job, then perhaps you could consider other options.</p>
<p>Now that I have answered the main question in short, let me expand this information in more detail to understand if it is really a good idea to learn Flutter in 2024.</p>
<h2 id="heading-what-makes-flutter-different">What makes Flutter different?</h2>
<p>Let's understand for a moment the strengths and distinctive points of Flutter to be able to make a future projection of where this framework is headed:</p>
<ul>
<li><p>Flutter allows you to develop for Android, iOS, web, Linux, macOS, and Windows with a single codebase.</p>
</li>
<li><p>Flutter has a feature called "hot reload", which allows you to see the changes you make to the code instantly on your device, significantly shortening development times.</p>
</li>
<li><p>Flutter contains a rich library of visual elements, many of them based on the Material design guidelines and many others based on Cupertino, the visual style of iOS and macOS. Additionally, by installing third-party packages it is possible to emulate the visual style of Linux or Windows, among others.</p>
</li>
<li><p>Flutter compiles to native code, which significantly increases its performance. The result is fast and light applications, which have nothing to envy of purely native development.</p>
</li>
<li><p>Flutter uses Dart, a programming language that is easy to learn, powerful, strongly typed, null-safe, and with many interesting capabilities for all types of environments.</p>
</li>
<li><p>Flutter has a strong and growing community due to its recent popularity and also Google's efforts to promote its use. This means that developers have a rich ecosystem of resources, including documentation, tutorials, YouTube channels like this one, forums, etc. where to ask for advice if you have any problem.</p>
</li>
</ul>
<p>Now that we are clear about the strengths of Flutter, let me tell you in which cases I recommend learning Flutter in 2024 and in which cases I don't.</p>
<h2 id="heading-why-you-should-learn-flutter">Why you should learn Flutter</h2>
<p>Let's consider several scenarios in which I encourage you to learn Flutter:</p>
<h3 id="heading-to-learn-new-skills"><strong>→ To learn new skills</strong></h3>
<p>Diving into Flutter is a fantastic way to broaden your technical horizons. If what interests you is simply the joy of learning, then Flutter will meet your expectations. With its unique approach to UI design, efficient code structure, and the use of Dart, learning Flutter can significantly enhance your understanding of mobile app development, opening doors to other facets of the industry, and helping you improve your programming skills.</p>
<h3 id="heading-to-evaluate-it-for-a-potential-project"><strong>→ To evaluate it for a potential project</strong></h3>
<p>If you're considering various frameworks for an upcoming project, getting hands-on with Flutter can be incredibly insightful. Its ability to deliver high-performance, visually appealing apps across multiple platforms from a single codebase makes it a strong contender. By learning Flutter, you can effectively assess its fit for your project's specific needs, such as user interface requirements, development timeline, and overall performance expectations.</p>
<h3 id="heading-to-create-prototypes-quickly"><strong>→ To create prototypes quickly</strong></h3>
<p>When it comes to turning ideas into tangible prototypes quickly, Flutter is a game-changer. Thanks to its vast widget library and hot reload feature, you can rapidly iterate designs and functionalities. This speed and flexibility are crucial in the early stages of development, where visualizing and modifying ideas quickly is key to successful project development and stakeholder communication.</p>
<h3 id="heading-to-cut-costs-migrating-from-native-to-cross-platform"><strong>→ To cut costs migrating from native to cross-platform</strong></h3>
<p>Transitioning from native app development to a cross-platform approach can be a strategic move to reduce costs and development time. Flutter stands out in this transition due to its single codebase feature, which eliminates the need to maintain separate codes for iOS and Android. This consolidation not only reduces initial development costs but also simplifies ongoing maintenance and updates, making it a cost-effective solution for long-term app development.</p>
<h3 id="heading-for-education-purposes"><strong>→ For education purposes</strong></h3>
<p>Whether you’re a student, educator, or self-learner, Flutter is an excellent educational tool. It’s designed to be intuitive and easy to learn, making it accessible for beginners. At the same time, its comprehensive capabilities offer depth for advanced learners. Flutter’s growing popularity also ensures that learners are gaining skills that are in demand in the current job market, providing a practical edge to their educational pursuits.</p>
<p>In each of these scenarios, Flutter's versatility, efficiency, and comprehensive features make it a standout choice for anyone looking to expand their app development skills.</p>
<h2 id="heading-why-you-shouldnt-learn-flutter">Why you shouldn't learn Flutter</h2>
<p>In the real world there are no generalities but rather each case is particular, for that same reason after seeing the specific cases in which I recommend learning Flutter, let's now see in which cases I recommend thinking carefully about it.</p>
<h3 id="heading-to-make-a-living-in-regions-where-there-is-no-demand"><strong>→</strong> To make a living... in regions where there is no demand</h3>
<p>Flutter is a great framework for creating applications, but it competes with other very good technologies such as React Native, which has been on the market longer, and also competes in a way against native development. If your only intention is to learn Flutter as a means of earning a living, then at least make sure that the region you live in is in demand for this type of technology. In some cases you may find that the demand for Flutter developers is low compared to the demand for native developers, for example. In this specific case, I would recommend that you choose wisely in which technology you are going to invest your time and energy.</p>
<p>If, on the other hand, after having done a search for the current demand you see that there are quite a few companies that are demanding Flutter programmers, then in that case it could be a good career path.</p>
<h3 id="heading-for-applications-requiring-a-high-level-of-performance"><strong>→</strong> For applications requiring a high level of performance</h3>
<p>This point sounds contradictory given that in the previous section I told you that Flutter has practically native performance. Let me qualify it:</p>
<p>For "normal" applications, without very specific performance requirements other than the mere fact that it runs fast and smoothly, then Flutter is a great candidate. However, at this point I am not talking about those types of applications. At this point I am referring to applications that have performance requirements well above average, we are talking for example about video editors, graphics engines, etc.</p>
<p>In this specific case, if your application is going to require this intensive level of performance, it is preferable that you consider building it natively, perhaps programming the most critical parts in C++ or Rust.</p>
<h3 id="heading-for-apps-that-require-a-lot-of-native-integrations-or-low-level-features"><strong>→</strong> For apps that require a lot of native integrations or low-level features</h3>
<p>Flutter offers a fantastic bridge to connect the parts written in Dart with the native part of the application. In this way, even if we are creating an application with Flutter, it is possible to code some parts directly in the native language, then we can take advantage of its performance and integrations that are only available at the native level.</p>
<p>Now, if your application is going to be full of these integrations, to the point that we are going to have to use this communication bridge all the time, I think it would be better to write it directly natively.</p>
<p>If the native bridge is required frequently, then the primary benefits of Flutter are largely negated. In such scenarios, you're not truly engaging in cross-platform development, nor are you fully committing to native development. Therefore, in these situations, I would advise opting for a fully native approach.</p>
<h3 id="heading-if-there-is-already-in-house-knowledge-of-native-programming"><strong>→</strong> If there is already in-house knowledge of native programming</h3>
<p>If your team already possesses significant expertise in native development, it might be more prudent to leverage this existing knowledge rather than immediately adopting a new technology.</p>
<p>While Flutter is user-friendly and relatively easy to master, it still demands time and dedication to learn. If your company or organization already has the capability to develop applications using native languages, it could be beneficial to carefully consider the advantages and disadvantages of introducing Flutter. This thoughtful evaluation can guide a more informed decision about whether to integrate Flutter into your development process.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As you can see, the decision to invest time and effort into learning a technology like Flutter depends on your specific goals and needs. If, after conducting your research, you choose to learn Flutter, remember that this blog offers articles on Flutter development that could assist you during your learning journey.</p>
<p>Thank you for reading this article. I hope you found it informative and helpful. Should you wish to share any thoughts, I would be delighted to read them in the comments section.</p>
<p>Happy coding :)</p>
]]></content:encoded></item><item><title><![CDATA[Flutter 3.16 released! Android Impeller preview, Game Toolkit Updates, iOS extensions and more!]]></title><description><![CDATA[We now have the new version of Flutter available, Flutter 3.16, and it's packed with cool new stuff like Impeller in preview mode for Android, support for Predictive Back Navigation for Android 14 and Material 3 as the default visual style. Also, you...]]></description><link>https://davidserrano.io/flutter-3-16-released-android-impeller-preview-game-toolkit-updates-ios-extensions-and-more</link><guid isPermaLink="true">https://davidserrano.io/flutter-3-16-released-android-impeller-preview-game-toolkit-updates-ios-extensions-and-more</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Cross Platform App Development. ]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Thu, 16 Nov 2023 18:53:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1700160210766/0513b333-9700-4058-b08f-62b74ab14b85.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We now have the new version of Flutter available, <strong>Flutter 3.16</strong>, and it's packed with cool new stuff like Impeller in preview mode for Android, support for Predictive Back Navigation for Android 14 and Material 3 as the default visual style. Also, you can now use Flutter widgets in some iOS extensions. And for the game devs, there are some sweet updates in the Flutter Games Toolkit. This is the last major update we're getting this year, and it's all about making our development faster and our projects better, so let's see in detail the most notable new features of Flutter 3.16.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/gNhlGpYggVA">YouTube</a></div>
</div>

<h2 id="heading-impeller-in-preview-mode-for-android">Impeller in preview mode for Android</h2>
<p>The new rendering engine of Flutter, Impeller, is now available in preview mode for Android on the stable channel. This version introduces a Vulkan backend designed to enhance the performance of Flutter apps on Vulkan-capable devices. The update shows significant improvements in frame rasterization times, leading to less jank and higher framerates.</p>
<p>However, Impeller is still not expected to work well on older devices that do not have Vulkan support. The Flutter team is still working on the backend for OpenGL so we will have to wait a little longer to see good performance on these types of devices.</p>
<p>You can now try Impeller on devices that support Vulkan by passing the <code>--enable-impeller</code> flag to the <code>flutter run</code> command or by adding the following section to the project's <code>AndroidManifest.xml</code> under the <code>&lt;application&gt;</code> tag:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta-data</span>
  <span class="hljs-attr">android:name</span>=<span class="hljs-string">"io.flutter.embedding.android.EnableImpeller"</span>
  <span class="hljs-attr">android:value</span>=<span class="hljs-string">"true"</span> /&gt;</span>
</code></pre>
<p>Besides working on the Vulkan Backend, the Flutter team has been working hard to improve text performance in Impeller for both Android and iOS. They've made changes that help manage text better and reduce slowdowns, especially in apps with a lot of text. This has made these apps smoother and more responsive. Additionally, the team has focused on fixing various issues and enhancing overall quality and stability for users on both platforms. They've been really proactive, addressing a lot of user feedback and making numerous updates to improve the overall experience.</p>
<h2 id="heading-material-3-is-now-the-default-visual-style">Material 3 is now the default visual style</h2>
<p>Starting with the 3.10 release in May 2023, Flutter's Material library has been updated to align with the latest Material Design guidelines. This update includes new components, themes, and refreshed visuals for existing components. Previously, to use these updates, developers had to opt-in by setting a theme flag. But now, with this new release, the Material 3 visual style is the default setting on Flutter apps.</p>
<p>If you prefer the older Material 2 version, you can still opt out of Material 3 by setting the <code>useMaterial3</code> property in your <code>MaterialApp</code> widget to <code>false</code>. However, it's worth mentioning that <strong>Material 2 will eventually be deprecated</strong>. Some widgets in Material 3 are completely new implementations, so when upgrading, you might notice changes in your app's UI. To address this, you should manually migrate to the new widgets.</p>
<p>Remember that Flutter also offers a demo application where you can experiment with all the Material 3 components and compare them with the previous version. The look of Material 3 components is mainly defined by the color and text themes set in the app's ThemeData. Developers can create a Material 3 color scheme easily with tools that generate pleasing and accessible color schemes, either from a base color or even from the dominant colors in an image.</p>
<h2 id="heading-predictive-back-navigation-in-android-and-ios-app-extensions">Predictive Back Navigation in Android and iOS app extensions</h2>
<p>In this latest Flutter update, there are exciting enhancements for both Android and iOS platforms. On the Android side, a significant improvement has been made to the mouse scroll wheel support. Before this update, using a mouse scroll wheel on Android tablets or foldables was a bit clunky, requiring considerable movement for any scrolling action on the screen. Now, Flutter has enhanced this feature, ensuring that scrolling with a mouse feels more natural and matches the scroll speed typical on Android devices.</p>
<p>Another key update for Android is the introduction of predictive back navigation, a feature from Android 14. This feature allows users to use the back gesture on their device to quickly see the home screen behind the current screen. Flutter's integration of this feature brings a more intuitive and consistent user experience, aligning with the latest Android functionalities.</p>
<p>On the iOS front, Flutter has expanded its capabilities to include some iOS app extensions. This means that developers can now use Flutter widgets to design the UI for certain types of iOS app extensions. However, this functionality is not universal across all types of app extensions, as some might have API limitations or memory restrictions.</p>
<h2 id="heading-exciting-news-about-the-flutter-games-toolkit">Exciting news about the Flutter Games Toolkit</h2>
<p>Flutter has seen a significant rise in its use for casual game development over the past few years. A large number of games, ranging from simple puzzles to complex arcade games, have been created using Flutter.</p>
<p>To further support game developers, Flutter is now releasing a major update to its <a target="_blank" href="https://flutter.dev/games">Flutter Casual Games Toolkit</a>. This update includes a variety of new resources aimed at helping developers progress from initial concepts to fully launched games. The toolkit now offers more genre-specific templates, such as those for card games and endless runner games, and integrates various services like Play Games Services, in-app purchases, ads, achievements, Crashlytics, and multiplayer support.</p>
<h2 id="heading-new-flutter-favorites-packages">New Flutter Favorites packages!</h2>
<p>Flutter's community and tools are always growing and changing, and with that, the Flutter Favorite program has been refreshed and updated! This is an exciting time for developers as the Flutter Ecosystem Committee has recently recognized a selection of packages as new Flutter Favorites. These include our beloved <a target="_blank" href="https://pub.dev/packages/flame">flame</a> game engine, the <a target="_blank" href="https://pub.dev/packages/macos_ui">macos_ui</a> package to make your macOS applications look native, and the wonderful <a target="_blank" href="https://pub.dev/packages/riverpod">riverpod</a> state manager package.</p>
<p>Keep an eye out for future Flutter Favorites as this program continues to spotlight exceptional packages and plugins. If you want to nominate a package or plugin to this program to become a Flutter Favorite you can do so by sending an email to the committee at <a target="_blank" href="mailto:flutter-committee@googlegroups.com">flutter-committee@googlegroups.com</a>.</p>
<h2 id="heading-whats-new-in-flutter-devtools">What's new in Flutter DevTools</h2>
<p>Flutter's DevTools has introduced a new extensions framework that offers several exciting possibilities. Now, package authors can create custom tools for their packages, which will show up directly in DevTools. They can also make use of existing DevTools frameworks and utilities to build powerful tools. For Dart and Flutter developers, this means they'll have access to specialized tools that are tailored to their specific app development needs, based on the app's dependencies. Authors of popular packages like Provider, Drift, and Patrol have started building this ecosystem, and you can use their DevTools extensions right now.</p>
<p>In addition to these extensions, the latest DevTools release brings a bunch of improvements and new features. Highlights include the addition of support for DevTools extensions and a new "Home" screen that offers a summary of your connected app. Other improvements focus on enhancing overall performance, the robustness of hot restarts, text selection and copying behavior, as well as polishing the network profiler response viewer.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>These are just a few of the key updates in Flutter version 3.16. With the Material 3 update almost fully implemented, and Impeller showing great performance on iOS, we're now looking forward to seeing how it performs on Android and other platforms. Meanwhile, the Flutter team is continuously working hard, adding new features and making performance enhancements. We will also keep an eye on the latest developments in Flutter's packages and plugin ecosystem.</p>
<p>For more in-depth information on all these updates and more, you can check out the <a target="_blank" href="https://medium.com/flutter/whats-new-in-flutter-3-16-dba6cb1015d1">official blog post</a>. Thanks for sticking with this summary to the end, and I hope you have a wonderful rest of your day.</p>
<p>Happy coding :)</p>
]]></content:encoded></item><item><title><![CDATA[Insane new App/Game launch requirements on Android 😰]]></title><description><![CDATA[Google announced the other day its new plans regarding changes to the policy that governs developers and applications published on Google Play. In its tireless fight against malware, fraud and low-quality applications, Google has decided to introduce...]]></description><link>https://davidserrano.io/insane-new-app-game-launch-requirements-on-android-google-play</link><guid isPermaLink="true">https://davidserrano.io/insane-new-app-game-launch-requirements-on-android-google-play</guid><category><![CDATA[Android]]></category><category><![CDATA[indiedev]]></category><category><![CDATA[Policy]]></category><category><![CDATA[android app development]]></category><category><![CDATA[android development]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Mon, 13 Nov 2023 18:08:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1699898639731/fd39b556-2891-4802-a338-abc830841f4a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Google announced the other day its new plans regarding changes to the policy that governs developers and applications published on Google Play. In its tireless fight against malware, fraud and low-quality applications, Google has decided to introduce a fundamental change that can harm the dreams of many indie game or app developers, if not end those dreams completely.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/01xI01GXzlo">YouTube</a></div>
</div>

<p>In <a target="_blank" href="https://android-developers.googleblog.com/2023/11/ensuring-high-quality-apps-on-google-play.html">this article</a> published on the official Android Developers Blog, we can see a summary of these changes. To start, Google is going to start asking for identity verification information for all developers who publish games or applications on Google Play, but this is not the particular change I want to talk about in this article.</p>
<p>If we go to the next point we can read the following:</p>
<blockquote>
<p>Developers with newly created personal Play Console accounts will soon be required to test their apps with at least 20 people for a minimum of two weeks before applying for access to production. This will allow developers to test their app, identify issues, get feedback, and ensure that everything is ready before they launch.</p>
</blockquote>
<p>This means that if you have created a developer account on Google Play after November 13, 2023, or if you plan to create one, you will necessarily have to go through a two-week testing phase in which you will be responsible for finding 20 people who want to test your application or game. This will be an essential requirement to be able to launch your application or game in production, that is, make it available to all Google Play users.</p>
<p>Now, what exactly is this minimum of 20 testers? Well, 20 people will have to give you their Google email address so that you can add them as testers of your application, they will have to download your app or game, and they will have to test it. And how much will they have to test it? Does it help if each of those 20 people downloads it and tries it for 5 minutes once? As far as I know, the Google Play team has not yet provided more details about it, but if we go to this <a target="_blank" href="https://support.google.com/googleplay/android-developer/answer/14151465">official documentation</a> page we can read the following:</p>
<blockquote>
<p>If you have a newly created personal developer account, you must run a closed test for your app with a minimum of 20 testers who have been opted-in for at least the last 14 days continuously.</p>
</blockquote>
<p>The question here is: what exactly does this <em>continuously</em> mean? Does this mean that these 20 testers will have to be testing your app or game each day of those 2 weeks? Does this mean that there has to be a certain number of openings for it to count as valid testing?</p>
<p>These are all questions that unfortunately we do not know for now. And why does Google do this? Well, in the statement that I mentioned previously, we can read:</p>
<blockquote>
<p>Developers who regularly use Play's app testing tools before publishing release higher-quality apps and games, which can lead to higher ratings and more success on Google Play. In fact, apps that use our testing tools have on average 3 times the amount of app installs and user engagement compared to those that don't.</p>
</blockquote>
<p>Well, it seems that what Google intends here is to put an end to the appearance of hundreds of applications that do not contribute anything to the ecosystem and that, supposedly, reduce the general quality of the software that we can find on Google Play.</p>
<p>And now I want to focus on an important factor: this will only affect the applications or games of developers who have a personal account. <strong>If you have a company account, this restriction will not affect you</strong>, you will be able to deploy applications and games without them having gone through any mandatory testing phase.</p>
<p>Some individuals have started to highlight the concern that the current efforts might undermine the capacity of independent developers to launch new apps and games. While I acknowledge that numerous releases may be of subpar quality, it's important to remember that there are very good applications and games that started precisely this way, with very humble beginnings but that little by little managed to cover an existing need in the market.</p>
<p>Furthermore, although we are told that this will only affect newly created personal accounts, I would not be surprised to see this policy also extended in the future to any product from any developer, old or new, who wants to launch a new application or game on Google Play.</p>
<p>I don't know about you, but I can't easily have 20 people to ask them to test my applications continuously for two weeks; and probably most indie developers neither. It's quite likely that if this policy is implemented in this way, businesses will begin to emerge, I cannot say whether legitimate and safe or not, but for a modest price will provide us with a group of <em>paid</em> testers with whom we can test new applications. In the end, it will be a new stopper for new developers, who will have to take into account this initial investment without knowing if their application or game will have any kind of success or not.</p>
<p>In my opinion, I completely understand that Google wants to have safe and useful applications in its store, however, this does not seem like the right way to achieve it. If you want to discourage the creation of junk apps then you could do what Apple does: ask for an annual fee for developers who want to publish in your store. Right now to be a Google Play developer you have to pay an initial fee of $25. This is a one-time payment that is made at the beginning and gives you the possibility of being a developer without ever paying anything else. To publish in Apple's AppStore you have to pay a fee of $99 annually, meaning that every year you will have to pay that money. This means that only those who have a minimum of interest and desire to create good products agree to publish in the store, even if they are small indie developers.</p>
<p>The truth is that I feel that being an Android developer is getting harder and harder every day, every year more and more policies are approved that restrict the ability we have to innovate and create new products. Furthermore, we have decisions like this that make creating applications or games on Google Play an exhausting task as time passes. I predict a future on Google Play dominated by corporations, those large companies with the capacity to cope with all the dedication that is necessary to stay up to date with new policies and to adapt applications to the increasingly numerous restrictions. All this while small developers go elsewhere. It is this group of indie developers who provide creativity, who provide novelty, and keep the ecosystem fresh. I have been doing this for more than 10 years and it is a shame to see the decline that Google Play and Android in general are having.</p>
<p>However, this is just my personal opinion. Tell me what you think of these changes in the comments section if you want. Thank you for reading until the end, I hope you have a nice rest of your day.</p>
]]></content:encoded></item><item><title><![CDATA[Build your knowledge base with Spreading]]></title><description><![CDATA[Technology advances very quickly, and that implies that the entire flow of information, good practices, systems, and know-how that circulate within a development team, within a sector, or an entire organization also circulates quickly and dynamically...]]></description><link>https://davidserrano.io/build-your-knowledge-base-with-spreading-the-ai-powered-knowledge-base-platform-for-developers</link><guid isPermaLink="true">https://davidserrano.io/build-your-knowledge-base-with-spreading-the-ai-powered-knowledge-base-platform-for-developers</guid><category><![CDATA[knowledge]]></category><category><![CDATA[KnowledgeManagement]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[Developer Tools]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Tue, 24 Oct 2023 10:59:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1698136177787/8481bca0-e3ae-4dd3-afca-47ed12aef0ff.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Technology advances very quickly, and that implies that the entire flow of information, good practices, systems, and know-how that circulate within a development team, within a sector, or an entire organization also circulates quickly and dynamically.</p>
<p>Finding the necessary information quickly and accurately can become a challenge if you do not have the necessary tools, and that is why in today's article I am going to talk to you about <a target="_blank" href="https://app.spreading.ai/">Spreading</a>: an AI-powered knowledge base platform for developers, which makes it easy to build a self-service knowledge base for your customers and developers.</p>
<p>This article is sponsored by <a target="_blank" href="https://www.zegocloud.com/">ZEGOCLOUD</a>, a leading company in the development of high-quality products for businesses and developers. Spreading is its new product based on artificial intelligence that can help you build your knowledge base quickly and easily, and the best part is that is <strong>100% free, so you can instantly build a self-service knowledge base with Spreading to your customers now!</strong></p>
<p>But first of all, let's see what exactly a knowledge base is and what functions it performs.</p>
<h2 id="heading-what-is-a-knowledge-base-and-why-do-i-need-one">What is a knowledge base and why do I need one?</h2>
<p>At its core, a knowledge base is a centralized repository for information, a structured compendium of insights and answers. Think of it as an encyclopedia for your product, service, or platform, where all pertinent details, from FAQs to intricate technical explanations, reside.</p>
<p>A knowledge base serves as a pivotal resource not only for developers but also as a strategic advantage for product owners. By equipping developers with essential information, it paves the way for superior and more robust applications, elevating the user experience. This centralized information hub leads to a noticeable drop in support-related queries, enabling the support team to address difficult challenges. It further creates a channel for user feedback, aiding product enhancement based on real-world insights. Additionally, by presenting a thorough knowledge base, brands position themselves as industry frontrunners, emphasizing their dedication to the developer community and their commitment to excellence.</p>
<p>Now that we have a basic understanding of what a knowledge base is and what it is for, let's see how Spreading can help us create one.</p>
<h2 id="heading-introducing-spreading-the-all-in-one-ai-knowledge-base-builder">Introducing Spreading: the all-in-one AI knowledge base builder</h2>
<p>To start seeing how Spreading can help us, let's first go to its <a target="_blank" href="https://app.spreading.ai/login/">registration page</a> to create a 100% free account with which we can start testing the product.</p>
<p>We are going to create a page by clicking on the button located at the top right titled <em>Add a page</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697887206722/de63d7e0-7ab3-497c-bc13-cc6ba6ef435b.png" alt="Press the &quot;Add a page&quot; button to start writing a page for our knowledge base." class="image--center mx-auto" /></p>
<p>A new file will appear, we click on it to open it, and then we click on the button located at the top right titled <em>Edit</em> to start editing it.</p>
<p>At this point, there are different ways in which Spreading can assist us in creating our document. Let's imagine that we want to create an explanation document on how to generate a sales report on our platform. In order to work on this document, we will first need a base template of the points to explain.</p>
<p>To do this, let's click on the <em>Generate outline</em> button:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697887980965/814ddcfd-1f9a-487a-95b1-9ac12044fd2d.png" alt="Click on &quot;Generate outline&quot; to create a template to work on." class="image--center mx-auto" /></p>
<p>Then we will have to choose the type of documentation to generate, we are going to choose <em>User guide</em> since what we want is for this document to serve as an explanation of how to do something to a specific user. As for the target audience, let's imagine that this feature of our product is sales-oriented, so we will choose <em>Sales</em> so the AI adapts the language appropriately:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697888035963/5e60ce1f-f9ad-4f0a-b841-f2744f1b497c.png" alt="Choose the &quot;User guide&quot; and &quot;Sales&quot; option." class="image--center mx-auto" /></p>
<p>The next step is to give the artificial intelligence a little more context so that it knows what it has to explain. We are going to add context information and click on the <em>Generate</em> button:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697888065615/99cd5ed4-9acb-4307-aaed-7f82a35ae2ca.png" alt="Let's give the AI a little more context so it understands what we want to create." class="image--center mx-auto" /></p>
<p>And in this simple way, we now have a base template on which to start working on our document:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697888118422/06cfeb88-3669-4dba-b680-1a8a0d83834b.png" alt="Spreading has generated an outline on which to start working." class="image--center mx-auto" /></p>
<h2 id="heading-exploring-more-features-spreading-has-for-document-generation">Exploring more features Spreading has for document generation</h2>
<p>We now have a base on which to start working. This document is still a text document, so we could just start typing. However, we are going to save some time by having the integrated AI auto-generate more content for us.</p>
<p>Continuing with the example, suppose we want to explain what benefits generating a sales report can have. For the AI to help us, we can simply enter a guide text, then select it and click on the AI button to see what options we have:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697889350193/a45576a2-6f44-4e5a-b41b-07fabc559d98.png" alt="Enter some guidance text, select it and press the AI button to see the auto-generation options." class="image--center mx-auto" /></p>
<p>In this case, I have chosen the option of expanding the information provided. In this way, the AI will generate a richer text with more details:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697889394106/74d4c433-a701-4c67-92ab-d85100f47785.png" alt="The artificial intelligence has expanded the guide text that we have written, providing more details." class="image--center mx-auto" /></p>
<p>Once this text is generated, we can make it longer if we consider it necessary and insert it into the document</p>
<h2 id="heading-advantages-of-spreading-for-creating-developer-documentation">Advantages of Spreading for creating developer documentation</h2>
<p>Another quite interesting function of Spreading is the help it gives us to generate documentation with code examples aimed at software developers.</p>
<p>Let's see it better with an example. We are going to generate a blank document and we are going to write the <strong>/</strong> character so we can see the list of available options. Let's choose <em>Code Block</em>. A blank code block will open:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697890084343/f3b51da7-35b4-4a9a-b7e0-9f42f2212970.png" alt="A block of code appears." class="image--center mx-auto" /></p>
<p>At this point, we can help ourselves with the integrated AI to perform various functions. For example, one of the most important features of a developer-oriented knowledge base is to expose a code example and explain what it is for. To do this we can copy the code example within the block, then enable the AI in the button located at the top right and ask it to explain to us what that code is for.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697890131622/6bc11af8-2fad-407c-9c98-ca7e9a5c1b86.png" alt="We copy the code into the block and enable the AI." class="image--center mx-auto" /></p>
<p>The AI suggests an explanation that we could add to improve the understanding of the code block:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697890177833/2ffa43b9-a677-4cf0-b26e-f76348dd5898.png" alt="The AI offers us an explanation of the provided code." class="image--center mx-auto" /></p>
<p>If we click on the <em>insert</em> button, we will have auto-generated an entire explanation of the code provided automatically:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697890218147/2f9abe84-d76d-4c94-9871-7453f95f2edf.png" alt="We click on &quot;Insert&quot; and we now have the completely auto-generated explanation." class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As you can see, creating a knowledge base is very quick and easy with <a target="_blank" href="https://app.spreading.ai/">Spreading</a>. From its ease of use to the variety of built-in features it includes, both text customization and styling, to the built-in artificial intelligence features, as well as the fact that it is <strong>completely free</strong>, make this product very important to take into account if you want to be as efficient as possible when you have to create your knowledge database.</p>
<p>Thanks to <a target="_blank" href="https://www.zegocloud.com/">ZEGOCLOUD</a> for sponsoring this article, I hope you found it useful and informative.</p>
<p>See you in the next article.</p>
<p>Happy coding :)</p>
]]></content:encoded></item><item><title><![CDATA[What Godot devs need to know about this new EU law (Cyber Resilience Act)]]></title><description><![CDATA[The Cyber Resilience Act, or CRA, is a new legislation that aims to establish new cybersecurity requirements for devices and software marketed in the European Union.
But what impact can the CRA have in terms of video game development, and more specif...]]></description><link>https://davidserrano.io/what-godot-devs-need-to-know-about-this-new-eu-law-cyber-resilience-act</link><guid isPermaLink="true">https://davidserrano.io/what-godot-devs-need-to-know-about-this-new-eu-law-cyber-resilience-act</guid><category><![CDATA[Godot]]></category><category><![CDATA[Godot 4]]></category><category><![CDATA[legislation]]></category><category><![CDATA[Europe]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Fri, 20 Oct 2023 14:33:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1697812266006/1b6c9290-04a1-48dd-8c5b-1d87485bd8a8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The Cyber Resilience Act, or <em>CRA</em>, is a new legislation that aims to establish new cybersecurity requirements for devices and software marketed in the European Union.</p>
<p>But what impact can the CRA have in terms of video game development, and more specifically, what impact can it have on Godot or your game if you consider selling it in any European country?</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/TWv-LweJaco">YouTube</a></div>
</div>

<p>In this article I am going to try to delve a little deeper into this topic, but, before going into detail, I would like to clarify two things:</p>
<ul>
<li><p>First: at the time of writing this article, all we know is what is written in a <a target="_blank" href="https://digital-strategy.ec.europa.eu/en/library/cyber-resilience-act">draft</a> of this new law, so everything discussed here is subject to change.</p>
</li>
<li><p>Second: I am not a legal expert, I am simply going to try to explain in simple terms what I have been able to find out and give my opinion about how it could affect the video game development industry.</p>
</li>
</ul>
<p>So without further ado let's try to figure out what CRA is, and what impact it could have on Godot and in games published in the EU.</p>
<h2 id="heading-what-is-the-cyber-resilience-act-or-cra">What is the Cyber Resilience Act, or CRA?</h2>
<p>Introduced by the European Union, the CRA seeks to elevate cybersecurity standards for digital devices and software intended for the EU market.</p>
<p>Both manufacturers and developers who market devices or software in the European Union, without taking into account their global location, must ensure the security of such digital products so they reach the market with fewer vulnerabilities and ensure that relevant quality standards are taken seriously. In addition, it's also intended that users can take this quality into account when consuming products with digital elements.</p>
<p>A significant shift of the CRA is its focus on software developers rather than the companies that use this software. The rationale? Developers are in a prime position to address vulnerabilities at their source, making software inherently safer.</p>
<p>However, not all software is seen as equal under the CRA. Based on its use and potential risks, software is divided into three categories.</p>
<ul>
<li><p>Non-critical products: It is estimated that the vast majority of products will be in this group. In my opinion, both Godot and video games would fall into this group.</p>
</li>
<li><p>Critical Class 1: This level includes software with higher-risk functions such as password managers, VPNs, network management systems, or remote access software.</p>
</li>
<li><p>Critical Class 2: Reserved for products that have the highest level of risk: operating systems, public key infrastructure and digital certification, general-purpose microprocessors or routers.</p>
</li>
</ul>
<p>Depending on its classification, software may face varying obligations. These range from comprehensive risk assessments to ensure software doesn't have known vulnerabilities and is configured for security by default, to extensive documentation detailing the product's design, vulnerabilities, and compliance with EU cybersecurity standards.</p>
<p>Additionally, while non-critical software developers can self-certify their products, those in the Critical Class categories must undergo assessments by external EU-certified auditors.</p>
<p>When vulnerabilities are detected, especially those under active exploitation, developers have a tight 24-hour window to report them to the European Union Agency for Cybersecurity.</p>
<p>Now that we have a general idea about what the CRA is and what new obligations it imposes, let's see what impact it can have on Godot and games released in European territory.</p>
<h2 id="heading-how-will-the-cra-impact-godot-and-games-published-in-the-eu">How will the CRA impact Godot and games published in the EU?</h2>
<p>In my opinion, based on the three groups of products discussed above, everything would seem to indicate that both video game engines and the video games themselves would fall within the group of non-critical products; given that its impact in terms of risk seems to be less pronounced than the products considered critical by this new rule.</p>
<p>But even as a non-critical product, both the Godot development team and any video game maker might not be exempt from the obligations that the CRA imposes. This act implies that developers, even of non-critical software, carry out a compliance self-assessment.</p>
<p>This self-assessment is essentially a commitment to ensure that their software complies with the cybersecurity standards outlined by the CRA. It means ensuring there are no known vulnerabilities, providing security patches, and maintaining a <em>secure by default</em> approach.</p>
<p>However, given the open-source nature of Godot, much like other free projects, this can generate additional complexities. This is because this type of software is distributed freely, without exhaustive control of who it is distributed to or for what reasons, therefore it can be harmful to impose this level of obligations on someone who simply delivers software freely without any type of cost.</p>
<p>For other open-source projects considered higher risk such as Linux distributions, the implications of the CRA could be more profound. Given their consideration as critical products, these projects could face much stricter regulations; which could discourage a company from delivering software as free software given this level of complexity and responsibility.</p>
<p>So far, everything seems to indicate that those projects considered "non-commercial" would be exempt from this rule, however, there are those who consider that an open-source project, if it receives financing even through donations on a regular basis, could be considered as commercial software and would have to comply with the CRA. Without a doubt Godot, given that it is frequently financed through altruistic donations, could fall into this group if this were confirmed.</p>
<p>As for video games marketed in Europe, they would undoubtedly also fall within the scope of the CRA as non-critical products, so they would be under the obligations discussed above.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As we reach the end of our deep dive into the CRA's potential implications, it's essential to remember that what we've discussed is based on the current draft of the act.</p>
<p>This means the provisions, classifications, and requirements can still change before the act is finalized and put into effect. The legislative process is both dynamic and responsive, and feedback from stakeholders, including the open-source community, can shape the final form of the act.</p>
<p>Throughout our exploration, we've uncovered the potential challenges the Godot team might face, from the intricacies of self-assessments to the added layers brought on by its open-source nature. But with every challenge, there lies an opportunity.</p>
<p>In this context, it’s an opportunity for the Godot community to rally together, employ enhanced security practices, and continue producing the remarkable software that countless developers rely on.</p>
<p>We'll continue to watch the development of the CRA, always hopeful and confident that the spirit of open-source collaboration will thrive, adapt, and lead the way into a secure, innovative future.</p>
]]></content:encoded></item><item><title><![CDATA[Game Programming Patterns in Godot: The Command Pattern]]></title><description><![CDATA[Have you ever been playing an adventure game and suddenly lost control of your character because a cutscene starts, revealing more of the story? Or in a strategy game or map editor, have you had the option to undo a recent action?
You've probably com...]]></description><link>https://davidserrano.io/game-programming-patterns-in-godot-the-command-pattern</link><guid isPermaLink="true">https://davidserrano.io/game-programming-patterns-in-godot-the-command-pattern</guid><category><![CDATA[Godot]]></category><category><![CDATA[Godot 4]]></category><category><![CDATA[patterns]]></category><category><![CDATA[architecture]]></category><category><![CDATA[clean code]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Sat, 14 Oct 2023 12:53:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1697287565923/a3cb02d6-24fe-4de9-ae1b-0437de599c09.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever been playing an adventure game and suddenly lost control of your character because a cutscene starts, revealing more of the story? Or in a strategy game or map editor, have you had the option to undo a recent action?</p>
<p>You've probably come across these situations many times, but have you ever wondered how they're coded? Is all that extra logic stuffed into the Player class?</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/nWUngckUbe8">YouTube</a></div>
</div>

<p>In this article, I'll dive into a popular design pattern that's super handy for these scenarios: <strong>the Command pattern</strong>.</p>
<p>This design pattern lets us wrap a request as an object, offering a range of flexibilities and capabilities I'll walk you through. I have created this example in which we can control a character, making him move and attack.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697113361738/4e377503-2b2b-475f-9b2e-34d6c68de668.png" alt class="image--center mx-auto" /></p>
<p>But I want that when he meets this wolf, I want an AI to take over, approach the wolf, and attack. Think of this as a cutscene where we want the main character to act out a specific part of the story. This could be followed by some kind of dialogue or anything else.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697113375536/58121e05-bb18-44a3-a07a-91e94745ccee.png" alt class="image--center mx-auto" /></p>
<p>Now, you might think about cramming all this logic into the Player class. But that's not ideal since it mixes responsibilities and further bloats one of the typically largest classes in gaming, which is the Player class.</p>
<p>This design pattern is eloquently detailed in a book called "Game Programming Patterns". I highly recommend reading it, since it provides an exhaustive review of several design patterns in software development applied to video game development, with code examples, explanations and insights on each pattern. While <a target="_blank" href="https://gameprogrammingpatterns.com/">it's available online</a> if you prefer physical copies as I do, here's a <a target="_blank" href="https://amzn.to/3QhtkSY">link for you to purchase</a> it in case you are interested (<em>Disclosure: As an Amazon Associate I earn from qualifying purchases</em>).</p>
<p>So if you are interested in learning how to use this design pattern in Godot to achieve cleaner, tidier, and more efficient code, keep reading!</p>
<h2 id="heading-creating-the-commands">Creating the Commands</h2>
<h3 id="heading-command">Command</h3>
<p>In the example I created, I am using the classic method to control the character, for example, if I want to get the input value to move it left or right:</p>
<pre><code class="lang-python">var movement_input = Input.get_action_strength(<span class="hljs-string">"move_right"</span>) - Input.get_action_strength(<span class="hljs-string">"move_left"</span>)
</code></pre>
<p>Or if I want to detect if the attack key has been pressed:</p>
<pre><code class="lang-python">Input.is_action_just_pressed(<span class="hljs-string">"attack"</span>)
</code></pre>
<p>The idea is to replace all of this so that this logic is outside of the Player class.</p>
<p>To do this, the first thing we must create is the Command class:</p>
<pre><code class="lang-python">class_name Command
extends Object


func execute(player: Player, data: Object = null) -&gt; void:
    <span class="hljs-keyword">pass</span>
</code></pre>
<p>This class defines an execute() method in which it receives a Player instance and an optional "data" argument. The goal of this method is to execute the appropriate action on the Player. Note that at this point we are coupling this Command to the Player, making it unable to be used by any other entity. I do this to keep this tutorial as simple as possible, but in a real case instead of the Player, you could receive a base class from which all your actors inherit.</p>
<h3 id="heading-movementcommand">MovementCommand</h3>
<p>Now we are going to create the command that will allow the player to move:</p>
<pre><code class="lang-python">class_name MovementCommand
extends Command


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Params</span>:</span>
    var input: float

    func _init(input: float) -&gt; void:
        self.input = input


func execute(player: Player, data: Object = null) -&gt; void:
    <span class="hljs-keyword">if</span> data <span class="hljs-keyword">is</span> Params:
        player.move(data.input)
</code></pre>
<p>We create the MovementCommand class that inherits from Command, and we set a Params class where we are going to indicate the direction of movement using a float value, -1 for left, 0 for staying still, and 1 for moving to the right.</p>
<p>In the execute method what we have to do is call the appropriate Player method to perform the move.</p>
<h3 id="heading-attackcommand">AttackCommand</h3>
<p>We do the same for the attack command, but in this case it won't be necessary to create any class parameters since we do not need to know anything else:</p>
<pre><code class="lang-python">class_name AttackCommand
extends Command


func execute(player: Player, _data: Object = null):
    player.attack()
</code></pre>
<h2 id="heading-creating-the-controllers">Creating the controllers</h2>
<p>Now that we have the necessary commands created, we need something to issue them. I'm going to call this concept "Controller". The idea is to have a base class, and extend it to allow the player to control the character, and another to make the AI control it.</p>
<h3 id="heading-playercontroller">PlayerController</h3>
<p>Let's start by creating the PlayerController base class:</p>
<pre><code class="lang-python">class_name PlayerController
extends Node


var player: Player

var movement_command := MovementCommand.new()
var attack_command := AttackCommand.new()


func _init(player: Player) -&gt; void:
    self.player = player
</code></pre>
<p>As you can see, this class maintains a reference to Player and instantiates each of the relevant commands. We make this class inherit from Node since we are going to have to interact with the game loop.</p>
<h3 id="heading-humancontroller">HumanController</h3>
<p>This is when you will see how everything takes shape. We create HumanController with the following content:</p>
<pre><code class="lang-python">class_name HumanController
extends PlayerController


func _physics_process(_delta):
    var movement_input = Input.get_action_strength(<span class="hljs-string">"move_right"</span>) - Input.get_action_strength(<span class="hljs-string">"move_left"</span>)

    <span class="hljs-keyword">if</span> Input.is_action_just_pressed(<span class="hljs-string">"attack"</span>):
        attack_command.execute(player)
    <span class="hljs-keyword">else</span>:
        movement_command.execute(player, MovementCommand.Params.new(movement_input))
</code></pre>
<p>The idea will be to add this node as a child of the Player, this way we can detect the user's input and execute the appropriate command. To do this, we will first have to create a container for this or any other controller:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697113456282/bc1ac1dd-3b4e-4cd0-8de3-087f2b30aa90.png" alt class="image--center mx-auto" /></p>
<p>Now we will add the following logic to the Player class:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Maintain a reference to the current controller</span>
var _controller: PlayerController

<span class="hljs-comment"># Reference to the container that will contain the controller node</span>
<span class="hljs-meta">@onready var _controller_container = $ControllerContainer</span>

<span class="hljs-comment"># By default, we create a HumanController</span>
func _ready() -&gt; void:
    set_controller(HumanController.new(self))

<span class="hljs-comment"># We added this method to allow changing the active controller</span>
func set_controller(controller: PlayerController) -&gt; void:
    <span class="hljs-comment"># Delete all previous controllers</span>
    <span class="hljs-keyword">for</span> child <span class="hljs-keyword">in</span> _controller_container.get_children():
        child.queue_free()

    _controller = controller    
    _controller_container.add_child(_controller)
</code></pre>
<p>At this point, if everything has gone well, we should be able to run the game and control the main character as we did before.</p>
<h3 id="heading-aicontroller">AiController</h3>
<p>Next, we are going to create another class that inherits from PlayerController, but this time we will create a custom AI that will send the following commands:</p>
<pre><code class="lang-python">class_name AiController
extends PlayerController


var _init_time: int = <span class="hljs-number">0</span>
var _is_attack_end: bool = false


func _ready():
    _init_time = Time.get_ticks_msec()


func _physics_process(_delta):
    var current_time = Time.get_ticks_msec() - _init_time

    <span class="hljs-keyword">if</span> current_time &lt; <span class="hljs-number">3000</span>:
        movement_command.execute(player, MovementCommand.Params.new(<span class="hljs-number">0.0</span>))
    <span class="hljs-keyword">elif</span> current_time &lt; <span class="hljs-number">3900</span>:
        movement_command.execute(player, MovementCommand.Params.new(<span class="hljs-number">1.0</span>))
    <span class="hljs-keyword">elif</span> <span class="hljs-keyword">not</span> _is_attack_end:
        attack_command.execute(player)
    <span class="hljs-keyword">else</span>:
        movement_command.execute(player, MovementCommand.Params.new(<span class="hljs-number">0.0</span>))

<span class="hljs-comment"># In this example, the player class will tell this controller that </span>
<span class="hljs-comment"># the attack has ended. As I say, it's a quick example, </span>
<span class="hljs-comment"># there are better ways to manage this.</span>
func on_attack_end():
    _is_attack_end = true
</code></pre>
<p>In this example, the code that I have put inside <code>_physics_process()</code> is completely orientational. I recommend that you use more sophisticated mechanisms so that your AI is accurate, but I think you can already see the benefit that the Command pattern brings us.</p>
<h3 id="heading-let-ai-take-control">Let AI take control</h3>
<p>The last step we have left is to decide at what point the AI will take control of the character. For example, we could create an Area2D and add the following script to it:</p>
<pre><code class="lang-python">extends Area2D


func _on_body_entered(body):
    <span class="hljs-keyword">if</span> body <span class="hljs-keyword">is</span> Player:
        body.set_controller(AiController.new(body))
</code></pre>
<p>When the character touches this area, the controller will be changed from HumanController to AiController, and from that point on it will be our logic that will control the character's actions.</p>
<h2 id="heading-the-benefits-of-the-command-pattern">The benefits of the Command pattern</h2>
<p>As you have seen, we have just managed to decouple the control of the character from the Player class. Through this mechanism we can obtain other benefits, not only being able to control the player in the cutscenes, but also for example that the player begins to control an enemy, or conversely that an enemy begins to control the player.</p>
<p>Furthermore, one of the most widespread uses of this pattern is to be able to redo actions. If you think about it, we're encapsulating each action in a Command object, so we could store all of these objects and then be able to go back and redo actions already performed.</p>
<p>I hope you found this article useful, we'll see in the next one.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Can Godot screw us like Unity did?]]></title><description><![CDATA[Unity has just destroyed itself and along with it an entire community of video game developers who have put their effort, money, time and hope into it.

🎥
Video version available on YouTube


I think that by now we are all aware of the magnitude of ...]]></description><link>https://davidserrano.io/can-godot-screw-us-like-unity-did</link><guid isPermaLink="true">https://davidserrano.io/can-godot-screw-us-like-unity-did</guid><category><![CDATA[Godot]]></category><category><![CDATA[unity]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[opinion]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Mon, 18 Sep 2023 19:08:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695063997836/86813b58-d2c7-4e3f-94f9-d50208a342d8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Unity has just destroyed itself</strong> and along with it an entire community of video game developers who have put their effort, money, time and hope into it.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/6ZOSQsiU0DY">YouTube</a></div>
</div>

<p>I think that by now we are all aware of the <a target="_blank" href="https://www.theverge.com/2023/9/12/23870547/unit-price-change-game-development">magnitude of the tragedy</a>: Unity is going to start charging a <a target="_blank" href="https://unity.com/runtime-fee">runtime fee</a> of $0.2 every time a player installs a game. If the player installs your game 10 times? You pay 10 times that amount. If the player installs a pirate copy of your game? You pay for that pirated copy. If you have a mobile game and your profit margin is less than this 0.2$, which is not uncommon in the mobile industry, you're going bankrupt.</p>
<p>Studios of all sizes, as well as indie developers, are looking for other video game engines to be able to port their current games or as potential candidates for future projects. All this in search of a technology that can be trusted to continue working in a safe, predictable and sustainable way.</p>
<p>However, Unity has not only destroyed itself and its community but has managed to sow a wave of distrust toward the corporations and groups that control these engines. If Unity has done it and I decide to move to another engine, what stops the group that controls that new engine from doing the same to me in the future too? Will we always be subject to the decisions of a small group of greedy people who will control our livelihood without being able to do anything about it?</p>
<p>In this article, I intend to address this topic, especially concerning the open-source Godot video game engine. If you chose this engine over Unity, stay with me because I'm going to explain <strong>why Godot is probably miles away from being able to do as much damage to the community as Unity has done</strong>. Let me explain to you why.</p>
<h2 id="heading-why-is-godots-case-different-from-unitys">Why is Godot's case different from Unity's?</h2>
<p>To answer the question of why Godot is less likely to screw you than Unity, we must first understand what the contractual relationship is between a Unity user, and Unity Software Inc., the company that develops the engine.</p>
<p>When you download Unity Hub and proceed to install any of the versions of the engine, you accept the <a target="_blank" href="https://unity.com/es/legal/terms-of-service">terms of service</a> that they provide you. This document defines the contractual relationship between you, as a Unity user, and the company that develops it.</p>
<p>These terms of service include a provision that allows Unity Software Inc. to alter these terms. This may include the option to alter their rates for using the engine. It remains to be seen if how they have made these changes is completely legal, but it could be said that, unless you have agreed to a special contract with Unity and you are governed by the terms of the standard service, they can make these types of modifications unilaterally. So to be clear: <strong>yes, they can screw you any time they feel like it</strong>.</p>
<p>To make matters worse, the terms of service before the current version allowed you to take advantage of the terms of service in force concerning the year of release of a certain version of the Unity editor, that is, one could continue using last year's Unity version, along with the terms of service in effect last year, and that new changes to the current terms will not affect you. However, <a target="_blank" href="https://www.gamerbraves.com/unity-silently-deletes-github-repo-that-tracks-terms-of-service-changes-and-updated-its-license/">this clause was eliminated</a>, so we could say that the current situation has been premeditated and what they've done has been done so that all developers have to pay the new runtime fee without being able to do anything.</p>
<h2 id="heading-what-is-the-contractual-relationship-between-you-and-godot">What is the contractual relationship between you and Godot?</h2>
<p>Now let's see under what legal basis you are offered and allowed to use the Godot open-source game engine.</p>
<p>When you download and use Godot, you do so under the project's open-source license, in this case, the MIT license; which can easily be found in the <a target="_blank" href="https://github.com/godotengine/godot/blob/master/LICENSE.txt">open repository in the LICENSE.md file</a>.</p>
<p>The MIT license is known as, if not the most, one of the most permissive and least restrictive licenses in the entire open-source software ecosystem. It only takes up 20 lines and allows you to do practically anything:</p>
<ul>
<li><p>Use the engine for free.</p>
</li>
<li><p>Make video games or any other type of software with it and then distribute it, sell it, or for whatever.</p>
</li>
<li><p>It does not force you to maintain the same license in the derivative works (that is, the games you make with it). You can make completely proprietary games with it.</p>
</li>
<li><p>You can even take the engine, rename it and sell it as is.</p>
</li>
</ul>
<p>As you can see, the MIT license is designed so that you can do almost anything in almost any way. So as long as Godot is distributed under this license, one can safely use it without being afraid of hidden fees, price changes, or anything else that could turn your studio upside down from one day to the next.</p>
<p>Now, the question that arises next is: what if the license changes? How likely is it that Godot will no longer have the MIT license? What if it stops being open-source? Can a company acquire it?</p>
<h2 id="heading-why-i-doubt-godot-will-deviate-from-the-mit-license">Why I doubt Godot will deviate from the MIT license</h2>
<p>First, let's understand what the implications are of changing the license of an open-source project: to begin with, each of the contributions that each contributor has made throughout the life of the project belongs to them. That is, the copyright of the code that you provide when you contribute to an open-source project belongs to you, which in turn you license to the end user through the license chosen by the project.</p>
<p>This means that from a copyright point of view, Godot belongs to every one of its contributors. Therefore, for the project to change licenses, every one of them would have to accept that license change.</p>
<p>At the time of writing, there are a total of 2203 people who have contributed to the engine, so all of these 2203 people should agree and accept a new license for the engine.</p>
<p>This is something that can certainly happen. In fact, this is exactly what happened with the <a target="_blank" href="https://bevyengine.org/">Bevy engine</a>, in which a total of 246 contributors agreed to relicense the engine, as explained in this <a target="_blank" href="https://bevyengine.org/news/bevy-0-6/#dual-mit-apache-2-0-license">blog post</a>. However, in this case it was done for the good of the engine and its users.</p>
<p>Imagining a situation in which more than 2,000 Godot contributors agree to change the engine license and start screwing the community and its users seems quite surreal to me and unlikely to happen. Let's understand that we are not talking about greedy executives looking for money, but about technology enthusiasts who enjoy using the engine for free and contributing code to it.</p>
<h2 id="heading-you-can-fork-the-engine-and-create-a-copy-for-yourself">You can fork the engine and create a copy for yourself</h2>
<p>However, let's put ourselves for a moment in the worst-case scenario: all Godot contributors agree and decide to change the license to one that is much more restrictive or that somehow prevents you from continuing to create commercial games with the engine.</p>
<p>The solution in this case would also be relatively easy: <strong>fork the engine, create a copy for yourself and bypass the new license. It's that simple.</strong></p>
<p>It would not be ideal, since you would not be able to access the new contributions and new features that would be released, but at least it would give you a window of time in which to maneuver and look for alternatives.</p>
<p>In any case, I think the probability of something like this happening is very small.</p>
<p>Godot, like many other open-source projects, is run by people with a very different profile from that of an executive who joins a company to get rich. Don't get me wrong, I'm not saying that using Godot is 100% safe and that in no case can anything bad happen to you, I'm just giving my opinion and my arguments as to why I think it's unlikely that a similar situation like the one that is happening with Unity will occur, and if it did happen, I believe that the resulting damages would be smaller and much more controllable.</p>
<p>In any case, this is my opinion, and as a Godot user, if I have to highlight something positive about the current horrible Unity situation, it is the boost this is going to give to the engine.</p>
<p>When one door closes, another opens, and I think the door that is opening now with Godot is very hopeful and will be beneficial for everyone in the years to come.</p>
]]></content:encoded></item><item><title><![CDATA[AdMob UMP SDK in Flutter - Implement your GDPR dialog]]></title><description><![CDATA[AdMob is going to start requiring all publishers to use a Google-certified Consent Management Platform (CMP) to request data usage consent under the General Data Protection Regulation (GDPR), as explained in this article of their help center.

🎥
Vid...]]></description><link>https://davidserrano.io/admob-ump-sdk-in-flutter-implement-your-gdpr-dialog</link><guid isPermaLink="true">https://davidserrano.io/admob-ump-sdk-in-flutter-implement-your-gdpr-dialog</guid><category><![CDATA[Flutter]]></category><category><![CDATA[admob]]></category><category><![CDATA[ads]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[#gdpr]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Tue, 12 Sep 2023 10:30:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1694447490393/1ae65b3c-777b-4f3a-b1f8-b31abd9c80a3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AdMob is going to start requiring all publishers to use a Google-certified <em>Consent Management Platform</em> (CMP) to request data usage consent under the <em>General Data Protection Regulation</em> (GDPR), as explained in this <a target="_blank" href="https://support.google.com/admob/answer/13554116">article</a> of their help center.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/6qApJmvQEGk">YouTube</a></div>
</div>

<p>In this article, I am going to explain how to comply with this new policy using <a target="_blank" href="https://support.google.com/admob/answer/10113005">Google's own CMP</a>, which is a free library that, although it has its flaws, will at least allow us to comply with this new regulation.</p>
<p>Through this Flutter tutorial you will learn how to do the following:</p>
<ol>
<li><p>How to ask for consent the first time the user starts the application</p>
</li>
<li><p>How to allow the user to modify their privacy preferences per the GDPR</p>
</li>
<li><p>How to manage the expiration of consent to request it again when necessary</p>
</li>
</ol>
<p>To implement this, you only need to have the <a target="_blank" href="https://pub.dev/packages/google_mobile_ads">google_mobile_ads</a> plugin installed. If you already display ads in your app, you should have this plugin already. While there are other plugins for showing this consent message, the latest google_mobile_ads versions come with the right SDK, so there's no need for third-party plugins.</p>
<h2 id="heading-create-the-admob-ump-message-in-the-console">Create the AdMob UMP message in the console</h2>
<p>The first step we must take is to create the consent message in the AdMob interface. If you already have this message created then simply go to the next step.</p>
<p>Access the <a target="_blank" href="https://apps.admob.com/">AdMob console</a>, and go to the <strong>Privacy &amp; messaging</strong> section:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694424226033/b240bf5b-64c2-4b3a-9567-097a1eaff4cf.png" alt class="image--center mx-auto" /></p>
<p>Here you will see the different types of messages that you can create. Press the first button at the bottom right to create your message:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694424243909/afb09cda-9e8b-4a74-bdac-139b7a62d009.png" alt class="image--center mx-auto" /></p>
<p>In the next screen, press the <strong>Create message</strong> button to start creating your message.</p>
<p>You will be presented with a form with several options. First, choose all the apps that you want to apply this message to. Here you should choose the applications that serve ads to users from the European Union. Keep in mind that if you want to apply different styles (colors, fonts) you will have to create more than one message.</p>
<p>Then choose the languages in which you want to display the message, and the consent options:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694424294728/e7f81c4b-d2ce-4b0e-b196-75b56d207d64.png" alt class="image--center mx-auto" /></p>
<p>If you have doubts about which option is right for you, you should consult with a lawyer specialized in this topic. For my applications I have chosen the option <strong>Consent, Do not consent, or Manage options</strong> to ensure that I offer a clear, easy and free way for my users to not consent to the use of data; but as I say, <strong>I am not a lawyer and this is not legal advice</strong>, so please consult a professional who can guide you in your particular case.</p>
<p>The last step will be to choose whether we want to show this message only in regions subject to the GDPR or worldwide.</p>
<p>Once created you will be able to view and configure your message. At this point, I advise you to navigate through the interface since there are several configuration options, including the selection of colors, languages, spacing, etc. Simply configure the message to your liking so that it looks good in your application.</p>
<p>ℹ If you have more questions about the process of creating the privacy message, I recommend that you look at the <a target="_blank" href="https://support.google.com/admob/answer/10113207">official AdMob guide</a>, where you will find the details of all the settings.</p>
<h2 id="heading-create-a-class-that-manages-the-gdpr-dialog">Create a class that manages the GDPR dialog</h2>
<p>The Google UMP SDK can be integrated directly into any point of your application, however in this tutorial what we are going to do is create our own class that will manage it and then a screen that will use this class.</p>
<p>The reason I do it this way is that the first time a user starts your app after downloading it is a sensitive moment, and I think showing this message right at the beginning gives a bad user experience.</p>
<p>On the other hand, each successive app launch will require us to look at the consent status and see if we need to display the message, this can happen for various reasons:</p>
<ol>
<li><p>The first time the user opened the app, the device had no internet connection, so the message could not be displayed</p>
</li>
<li><p>The message has been modified and must be displayed again</p>
</li>
<li><p>Consent status has expired</p>
</li>
</ol>
<p>In these cases, we are going to have to show the message right when the application starts.</p>
<p>To cover all these needs, what I do is create a class that specializes in displaying this message and a screen that uses it. This gives us the flexibility to be able to display this message in different situations that you will see throughout this tutorial.</p>
<p>Create an <code>initialization_helper.dart</code> file with the following content:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:google_mobile_ads/google_mobile_ads.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InitializationHelper</span> </span>{
  <span class="hljs-keyword">void</span> initialize() {
    <span class="hljs-keyword">final</span> params = ConsentRequestParameters();
    ConsentInformation.instance.requestConsentInfoUpdate(params, () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">if</span> (<span class="hljs-keyword">await</span> ConsentInformation.instance.isConsentFormAvailable()) {
        <span class="hljs-comment">// Here we'll load the consent form</span>
      }
    }, (error) {
      <span class="hljs-comment">// Manage error</span>
    });
  }
}
</code></pre>
<p>In this method, we request the consent status, and if a form is available we will proceed to display it. The <code>isConsentFormAvailable()</code> method will only return <code>true</code> in cases where there is a form to display, if the user is outside of the countries subject to the GDPR this method will return <code>false</code>, although this behavior may vary depending on how you have configured the message.</p>
<p>Now we will create a private method that loads and displays the message:</p>
<pre><code class="lang-dart">  <span class="hljs-keyword">void</span> _loadConsentForm() {
    ConsentForm.loadConsentForm((consentForm) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> status = <span class="hljs-keyword">await</span> ConsentInformation.instance.getConsentStatus();
      <span class="hljs-keyword">if</span> (status == ConsentStatus.<span class="hljs-keyword">required</span>) {
        consentForm.<span class="hljs-keyword">show</span>((formError) {
          <span class="hljs-comment">// Call this method again, if the user has already selected an</span>
          <span class="hljs-comment">// option the message will not be displayed again.</span>
          _loadConsentForm();
        });
      }
    }, (FormError? error) {
      <span class="hljs-comment">// Handle error</span>
    });
  }
</code></pre>
<p>If you've noticed, this library doesn't use Futures to do asynchronous work but instead uses callbacks. This is not a very "Flutter" way of doing things, so we're going to "Flutterize" it using a <a target="_blank" href="https://api.flutter.dev/flutter/dart-async/Completer-class.html">Completer</a>. In addition, we are going to add another method that will be in charge of initializing the consent-dependent components:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:async'</span>;

  <span class="hljs-comment">// [...]</span>

  Future&lt;FormError?&gt; _loadConsentForm() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> completer = Completer&lt;FormError?&gt;();

    ConsentForm.loadConsentForm((consentForm) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> status = <span class="hljs-keyword">await</span> ConsentInformation.instance.getConsentStatus();
      <span class="hljs-keyword">if</span> (status == ConsentStatus.<span class="hljs-keyword">required</span>) {
        consentForm.<span class="hljs-keyword">show</span>((formError) {
          completer.complete(_loadConsentForm());
        });
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// The user has chosen an option,</span>
        <span class="hljs-comment">// it's time to initialize the ads component.</span>
        <span class="hljs-keyword">await</span> _initialize();
        completer.complete();
      }
    }, (FormError? error) {
      completer.complete(error);
    });

    <span class="hljs-keyword">return</span> completer.future;
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; _initialize() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> MobileAds.instance.initialize();

    <span class="hljs-comment"><span class="markdown">/<span class="hljs-strong">**
     <span class="hljs-emphasis">* Here </span></span></span>you<span class="markdown"><span class="hljs-strong"><span class="hljs-emphasis"> can place any other initialization of any
     *</span> other component that depends on consent management,
     <span class="hljs-emphasis">* for example </span></span></span>the<span class="markdown"><span class="hljs-strong"><span class="hljs-emphasis"> initialization of Google Analytics
     *</span> or Google Crashlytics would go here.
     <span class="hljs-emphasis">*/</span></span></span></span>
  }
</code></pre>
<p>We are now going to also use a <code>Completer</code> in the <code>initialize()</code> method, and also manage the initialization of the components:</p>
<pre><code class="lang-dart">  Future&lt;FormError?&gt; initialize() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> completer = Completer&lt;FormError?&gt;();

    <span class="hljs-keyword">final</span> params = ConsentRequestParameters();
    ConsentInformation.instance.requestConsentInfoUpdate(params, () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">if</span> (<span class="hljs-keyword">await</span> ConsentInformation.instance.isConsentFormAvailable()) {
        <span class="hljs-keyword">await</span> _loadConsentForm();
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// There is no message to display,</span>
        <span class="hljs-comment">// so initialize the components here.</span>
        <span class="hljs-keyword">await</span> _initialize();
      }

      completer.complete();
    }, (error) {
      completer.complete(error);
    });

    <span class="hljs-keyword">return</span> completer.future;
  }
</code></pre>
<p>The next step is to create a screen that the previous class will use to manage consent. We will place a loading to give feedback to the user that something is happening while the consent status and message are loading:</p>
<p>Create the file <code>initialize_screen.dart</code> with the following content:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:admob_consent_dialog/initialization_helper.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InitializeScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">final</span> Widget targetWidget;

  <span class="hljs-keyword">const</span> InitializeScreen({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.targetWidget});

  <span class="hljs-meta">@override</span>
  State&lt;InitializeScreen&gt; createState() =&gt; _InitializeScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_InitializeScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">InitializeScreen</span>&gt; </span>{
  <span class="hljs-keyword">final</span> _initializationHelper = InitializationHelper();

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();

    _initialize();
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) =&gt; <span class="hljs-keyword">const</span> Scaffold(
        body: Center(
          child: CircularProgressIndicator(),
        ),
      );

  Future&lt;<span class="hljs-keyword">void</span>&gt; _initialize() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> navigator = Navigator.of(context);

    WidgetsBinding.instance.addPostFrameCallback((_) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">await</span> _initializationHelper.initialize();
      navigator.pushReplacement(
        MaterialPageRoute(builder: (context) =&gt; widget.targetWidget),
      );
    });
  }
}
</code></pre>
<h2 id="heading-where-to-display-the-admob-consent-dialog">Where to display the AdMob consent dialog</h2>
<p>We can now use the created elements in the appropriate places. This varies from application to application, but as a suggestion, I can tell you what I do:</p>
<ol>
<li><p>The first time the user opens the application, the first thing I show them is an onboarding screen where I explain the strengths of my application. Once the onboarding is complete, I direct the user to the <code>InitializeScreen</code>, passing the main screen of my application in the <code>targetWidget</code> parameter.</p>
</li>
<li><p>If this is not the first time the user opens the application, the first screen I show is <code>InitializeScreen</code>, in case it is necessary to show the message again.</p>
</li>
</ol>
<p>It is important to keep in mind that the message will only be displayed according to the configuration we have chosen. For those users residing in countries not subject to the GDPR, what they will see is simply a short screen with a central loading. In iOS, if we have also configured the IDFA explanatory message, it may be displayed.</p>
<p>To verify that the implementation you have made works correctly we can alter the <code>ConsentRequestParameters</code> object in <code>initialization_helper.dart</code> as follows to simulate a location:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> params = ConsentRequestParameters(
  consentDebugSettings: ConsentDebugSettings(
    debugGeography: DebugGeography.debugGeographyEea,
    <span class="hljs-comment">// Or DebugGeography.debugGeographyNotEea to simulate outside of the EEA</span>
  ),
);
</code></pre>
<p>I recommend that you do the following tests to verify that everything is going well:</p>
<ol>
<li><p>Launch your newly installed app by forcing the location to be within the European Union: The message should be displayed.</p>
</li>
<li><p>Launch your newly installed app by forcing the location to be outside the European Union: The message should not be displayed (unless you have set it to worldwide).</p>
</li>
<li><p>Launch your newly installed app by forcing the location to be within the European Union, but REMOVE the internet connection, the message will not be able to be displayed. Then reconnect to the internet and start the application, the message should be displayed at startup.</p>
</li>
</ol>
<p>Another thing you should try is to reset the consent state, you can do this by running the following:</p>
<pre><code class="lang-dart">ConsentInformation.instance.reset();
</code></pre>
<p>Restart the app and verify that the message appears.</p>
<h2 id="heading-allow-the-user-to-modify-their-privacy-options">Allow the user to modify their privacy options</h2>
<p>The GDPR dictates that users must be able to modify any previously made choices, so we should add a button somewhere in our app (I have it in the settings screen, specifically in the <em>legal</em> section, next to the privacy policy) so that the user can reopen this consent message and modify their preferences.</p>
<p>To do this we will have to create a new method that allows us to open the consent dialog, but that has a somewhat different behavior from the previous initialization method.</p>
<p>Add this method to <code>initialization_helper.dart</code>:</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-built_in">bool</span>&gt; changePrivacyPreferences() <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> completer = Completer&lt;<span class="hljs-built_in">bool</span>&gt;();

  ConsentInformation.instance
      .requestConsentInfoUpdate(ConsentRequestParameters(), () <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">await</span> ConsentInformation.instance.isConsentFormAvailable()) {
      ConsentForm.loadConsentForm((consentForm) {
        consentForm.<span class="hljs-keyword">show</span>((formError) <span class="hljs-keyword">async</span> {
          <span class="hljs-keyword">await</span> _initialize();
          completer.complete(<span class="hljs-keyword">true</span>);
        });
      }, (formError) {
        completer.complete(<span class="hljs-keyword">false</span>);
      });
    } <span class="hljs-keyword">else</span> {
      completer.complete(<span class="hljs-keyword">false</span>);
    }
  }, (error) {
    completer.complete(<span class="hljs-keyword">false</span>);
  });

  <span class="hljs-keyword">return</span> completer.future;
}
</code></pre>
<p>If you look, this method is very similar to the previous one, but in this case once the user has made a decision we do not try to open the dialog again.</p>
<p>Allowing the user to change their privacy preferences only makes sense for users in countries subject to the GDPR. If you look at the official Google SDK documentation you will see that we are not provided with any explicit method to check this, however all parameters, including whether the user is under the GDPR, are stored in the local preferences by the SDK.</p>
<p>Specifically, the <em>IABTCF_gdprApplies</em> key contains a value of <code>1</code> if the user is under the GDPR. However, the problem of accessing this value can come if you use a plugin like <a target="_blank" href="https://pub.dev/packages/shared_preferences">shared_preferences</a>. This plugin, undoubtedly the most famous in the Flutter world for saving/reading local preference values, has the drawback that in its default configuration it adds a prefix to all keys. For that reason, if you try to access the value I mentioned, you will get a <code>null</code>, even if that value exists.</p>
<p>In this case, I recommend using any other mechanism to read the preferences without anything altering the keys. For this tutorial, I am going to use <a target="_blank" href="https://pub.dev/packages/async_preferences">async_preferences</a>, a plugin created by me that does not alter the keys used in the preferences in any way. You can install it using this command:</p>
<p><code>flutter pub add async_preferences</code></p>
<p>Below I show you an example code of a very simple settings screen where one of the options allows the user to reopen the consent dialog:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:admob_consent_dialog/initialization_helper.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:async_preferences/async_preferences.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SettingsScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">const</span> SettingsScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  State&lt;SettingsScreen&gt; createState() =&gt; _SettingsScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_SettingsScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">SettingsScreen</span>&gt; </span>{
  <span class="hljs-keyword">final</span> _initializationHelper = InitializationHelper();

  <span class="hljs-comment">// We will use a Future to read the setting that</span>
  <span class="hljs-comment">// tells us if the user is under the GDPR</span>
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> Future&lt;<span class="hljs-built_in">bool</span>&gt; _future;

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();

    _future = _isUnderGdpr();
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) =&gt; Scaffold(
        appBar: AppBar(
          title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Settings'</span>),
        ),
        body: FutureBuilder(
          future: _future,
          builder: (context, snapshot) =&gt; ListView(
            children: [
              <span class="hljs-comment">// Show it only if the user is under the GDPR</span>
              <span class="hljs-keyword">if</span> (snapshot.hasData &amp;&amp; snapshot.data == <span class="hljs-keyword">true</span>)
                ListTile(
                  title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Change privacy preferences'</span>),
                  onTap: () <span class="hljs-keyword">async</span> {
                    <span class="hljs-keyword">final</span> scaffoldMessenger = ScaffoldMessenger.of(context);

                    <span class="hljs-comment">// Show the consent message again</span>
                    <span class="hljs-keyword">final</span> didChangePreferences =
                        <span class="hljs-keyword">await</span> _initializationHelper.changePrivacyPreferences();

                    <span class="hljs-comment">// Give feedback to the user that their</span>
                    <span class="hljs-comment">// preferences have been correctly modified</span>
                    scaffoldMessenger.showSnackBar(
                      SnackBar(
                        content: Text(
                          didChangePreferences
                              ? <span class="hljs-string">'Your privacy choices have been updated'</span>
                              : <span class="hljs-string">'An error occurred while trying to change your privacy choices'</span>,
                        ),
                      ),
                    );
                  },
                ),
            ],
          ),
        ),
      );

  Future&lt;<span class="hljs-built_in">bool</span>&gt; _isUnderGdpr() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// Initialize AsyncPreferences and checks if the IABTCF_gdprApplies</span>
    <span class="hljs-comment">// parameter is 1, if it is the user is under the GDPR,</span>
    <span class="hljs-comment">// any other value could be interpreted as not under the GDPR</span>
    <span class="hljs-keyword">final</span> preferences = AsyncPreferences();
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> preferences.getInt(<span class="hljs-string">'IABTCF_gdprApplies'</span>) == <span class="hljs-number">1</span>;
  }
}
`
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As you can see, both in order to comply with the different privacy laws and to comply with the AdMob policy, there are many factors to take into account and several actions to take.</p>
<p>I hope this indicative tutorial has been useful to you, remember that it is simply an example of how to do it, and in your case you should adapt the solution that I propose here to your application.</p>
<p>We'll see in the next article,</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Why Linux Mint is the best Linux distribution]]></title><description><![CDATA[Linux Mint is the best Linux distribution for all those who are looking for a stable, minimalist and efficient operating system to work with... at least, in my opinion.

🎥
Video version available on YouTube and Odysee


If you have been reading some...]]></description><link>https://davidserrano.io/why-linux-mint-is-the-best-linux-distribution</link><guid isPermaLink="true">https://davidserrano.io/why-linux-mint-is-the-best-linux-distribution</guid><category><![CDATA[Linux]]></category><category><![CDATA[Mint]]></category><category><![CDATA[gnu]]></category><category><![CDATA[operating system]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Fri, 21 Jul 2023 07:46:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1689925475796/d6f34e31-3cd0-490d-b98b-17451433d638.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Linux Mint</strong> is <em>the best</em> Linux distribution for all those who are looking for a stable, minimalist and efficient operating system to work with... at least, in my opinion.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🎥</div>
<div data-node-type="callout-text">Video version available on <a target="_blank" href="https://youtu.be/y_h_PmIQABE">YouTube</a> and <a target="_blank" href="https://odysee.com/@svprdga:d/why-linux-mint-is-the-best-linux-distribution">Odysee</a></div>
</div>

<p>If you have been reading some of the articles on my blog, you will have seen that I often talk about macOS or the Mac Mini. And this is because this is the preferred machine that I use for the development of multiplatform apps, however, my main operating system, the one I use "by default", the one I use for personal and professional management, the one I use for my tech experiments, is, and has been for the past decade, <a target="_blank" href="https://linuxmint.com/">Linux Mint</a>.</p>
<p>And I say all this because this popular Linux distribution has earned through its own merits the fact that I consider it one of the best operating systems ever created, with all due respect to other Linux distributions.</p>
<p>Linux Mint was created in 2006, so at the time of writing this article is now 17 years old! I think is time to give some credit back and tell you why, from my point of view, Linux Mint is probably one of the best platforms for the vast majority of people, both those looking to switch from Windows or macOS and also those open-source, distro-hopping, passionate tech enthusiasts looking for the definitive distribution. Without further ado, let me tell you why <strong>Linux Mint is my ultimate Operating System</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689693413664/01b6b51f-7065-413a-9d5a-9afa1128ead6.png" alt="The pretty Linux Mint Cinnamon desktop" class="image--center mx-auto" /></p>
<h2 id="heading-it-just-works">"It just works"</h2>
<p>Have you heard this sentence before? <em>It just works</em> 🍏? The first time I experienced this feeling in the Linux world was with Linux Mint, and since then I have not experienced it again with any other distribution.</p>
<p>Like many people, I took my first steps in the world of Linux with Ubuntu, the well-known "user-friendly" distribution with which many people jump from Windows to GNU/Linux. My experience was terrible at that time, display errors, unexpected crashes, terrible performance... a very bad impression if you are new to the Linux universe. We are talking about more than 10 years ago, I am completely unaware of the current state of Ubuntu, but at least at that time and for me it was completely unusable.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689694682269/9a4cb260-8599-42c6-9027-15a65213c9cd.webp" alt="My first contact with GNU/Linux: Ubuntu 14.04" class="image--center mx-auto" /></p>
<p>Everything changed when I installed Linux Mint on an old laptop that I was about to retire and that I decided to give to my father. I knew perfectly well that if I handed him over the old laptop with Windows installed, it would take a matter of months for it to be filled with spyware and shady stuff, and ultimately the performance of the machine would gradually decline until it was unusable. That's why I searched for an <em>"easy-to-use Linux distribution"</em> and one of the results was Linux Mint. I downloaded it, installed it on the laptop and handed it over to my father. This happened over 7 years ago and to this day my father is still using this same machine with the same Linux Mint installation that I did for him at that time.</p>
<p>After that, at work, I was still using Ubuntu... but I was curious to install myself this same distribution that I had the pleasure of "tinkering" on the old computer that I gave to my father...so I decided to install it and from that moment I was able to see how the Linux universe could be a smooth experience, without errors, where everything worked as expected and where one could work in peace.</p>
<p>In my opinion, <strong>Linux Mint is one of the distributions that works best "out of the box"</strong>, and there are plenty of other people who seem to think the same way since it is currently ranked as the <strong>third most popular distro</strong> according to <a target="_blank" href="https://distrowatch.com/table.php?distribution=mint">DistroWatch</a>.</p>
<h2 id="heading-minimalist-experience">Minimalist experience</h2>
<p>The user experience at the level of the desktop interface and basic programs in Linux Mint is beautiful, friendly and simple. Specifically, I'm talking about the Cinnamon edition, which is the <em>default</em> desktop of Linux Mint. Along with this modern interface, one can also choose the Mate desktop, more robust and traditional; or Xfce, much lighter.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689696533062/47b0075e-351f-49a6-b64e-b7adbcf6f290.png" alt class="image--center mx-auto" /></p>
<p>The Cinnamon desktop is effectively reminiscent of the traditional Windows experience, with a taskbar located at the bottom, and a "Start" menu that appears when you press the button located at the bottom left. In the center we have the open windows and on the left a set of controls where the date and time are, access to the quick network panel, Bluetooth, etc.</p>
<p>Of the most widely used and well-known Linux desktops environments that most resemble Cinnamon is KDE, even though Cinnamon is actually a fork of Gnome 3. Cinnamon's goal is to provide a simple and elegant user interface that is easy for newcomers to use, but also powerful enough for advanced users.</p>
<p>In the latest releases, the Linux Mint development team decided to "modernize" the look of this desktop environment and made several changes which, in my opinion, have helped to further improve the smooth and elegant look of this distro. Specifically in version <code>21.1</code> they renewed the look and feel of the system by modifying colors and icons, and changing the traditional green that came by default to blue, although of course if you liked green you could configure it without problems.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689696668029/9ef5742e-c96d-430b-ad12-c343d847ffed.png" alt="Linux Mint 21.1 introduced new and exciting changes in its UI appearance." class="image--center mx-auto" /></p>
<p>Recently version <code>21.2</code> have gone further in its efforts to make things easy and allows you to choose the appearance and theme of the desktop quickly and easily by choosing between light, dark or mixed mode, a theme and an accent color. Of course, if customization is your thing, you still can choose among the myriad of available themes. Something that is greatly appreciated since it simplifies its use for new users while maintaining its high level of customization for more advanced users.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689696861738/787b3b12-4eaf-4131-b44a-e29e93be6a00.png" alt="In Linux Mint 21.2 you can easily customize the look of the interface by choosing a style, a dark, light, or mixed mode and an accent color." class="image--center mx-auto" /></p>
<p>In conclusion, <strong>Linux Mint's default desktop, Cinnamon, is an elegant, simple and easy-to-use desktop environment</strong>, with just the right customization options but without being overwhelming; with colors that make a nice contrast and modern look.</p>
<h2 id="heading-robust-stable-solid">Robust, stable, solid</h2>
<p>Linux Mint has a traditional philosophy regarding which versions of the software to use. It is positioned securely in terms of the programs it integrates and the packages that make up the system, which means that it will prioritize stable versions that have been highly tested and are known to work; versus newer versions that include more functionality but have been in production for less time.</p>
<p>This feature is certainly not for everyone. However, if what you are looking for is a distribution that tries to be as stable as possible, Linux Mint is for you since stability is one of its main goals.</p>
<p>It achieves this first of all by basing itself on the latest LTS (Long Term Support) version of Ubuntu as a base. From here, two versions are usually released per year, of which some of them are established as LTS versions, which will only receive critical or security updates.</p>
<p>In my opinion, this conservative model of dealing with new software is highly recommended if you want your machine to work and can't afford to have things breaking every now and then, or every time you upgrade. In all these years that I have been using Linux Mint, I have to say that I have hardly had any technical problems, everything has worked as expected except for a few exceptions, and that is saying a lot considering that we are talking about a complete operating system that is free of charge, carried out by a small team of people that is financed based on donations from the community and some sponsorships.</p>
<p>Despite all this, if you have any technical or stability problems with the system, you can always seek help in the <a target="_blank" href="https://forums.linuxmint.com/">official forum</a> where there is a wide community of people who will try to help you and give you support at no cost for any problems you may have.</p>
<p>As I mentioned earlier in this article, <strong>the stability of Linux Mint is in my opinion one of its strengths</strong>, since when I'm working I can't afford to be distracted by system errors or spend time fixing bugs that shouldn't be there.</p>
<h2 id="heading-the-philosophy-of-linux-mint">The philosophy of Linux Mint</h2>
<p>To conclude this article on Linux Mint, I want to emphasize an element I view as fundamental - the philosophy driving the project.</p>
<p>Linux Mint is a free and open-source project made up of a collection of programs and utilities that predominantly utilize the GPL license. This allows both individuals and corporations to utilize Linux Mint for personal and commercial objectives. The vast majority of the software included in the project can be freely used, copied, and altered in accordance with the open licenses it abides by.</p>
<p>Furthermore, Linux Mint is community-driven. The development team maintains consistent communication with the community, incorporating direct feedback to enhance the project.</p>
<p>Beyond all these, Linux Mint also stands as a great operating system with regard to the privacy it affords its users. Linux Mint does not employ tracking technologies to assemble personal profiles for commercial use or for any other purpose.</p>
<p>Given all of the above, I believe it's clear why I regard <strong>Linux Mint as an excellent resource for anyone who values freedom and privacy</strong>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope I have been able to show all the virtues that this Linux distribution has, and why many people could benefit from using it.</p>
<p><strong>If you use Windows and are fed up with all the bad stuff about it, I highly recommend you take a look at Linux Mint.</strong></p>
<p>If you're a Linux fan and haven't tried it yet, what are you waiting for?</p>
<p>I hope this article helps spread the word about this great operating system, and if, on the contrary, you have been using it for a long time and you are as passionate about it as I am, I would suggest that you <a target="_blank" href="https://www.linuxmint.com/getinvolved.php">consider contributing in some way to the project</a> so that it continues to grow and benefit thousands of people.</p>
<p>I hope this article has been illustrative to you, do you think that what I have exposed here coincides with your experience? What other Linux distributions do you think could rival Linux Mint? Leave me your comments below and I will gladly read them to start a debate.</p>
<p>See you in the next article!</p>
]]></content:encoded></item><item><title><![CDATA[Working with Files in Flutter]]></title><description><![CDATA[Odds are that if you work with Flutter, eventually you'll have to handle files and directories.
Let's do a quick summary of all the basic filesystem operations that we can do with Flutter:

📽 Video version available on YouTube and Odysee

https://yo...]]></description><link>https://davidserrano.io/working-with-files-in-flutter</link><guid isPermaLink="true">https://davidserrano.io/working-with-files-in-flutter</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Flutter Examples]]></category><category><![CDATA[file system]]></category><category><![CDATA[files]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Fri, 07 Jul 2023 12:30:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688732979624/4f272477-d71f-4f4c-9576-541be75fbec0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Odds are that if you work with Flutter, eventually you'll have to handle files and directories.</p>
<p>Let's do a quick summary of all the basic filesystem operations that we can do with Flutter:</p>
<blockquote>
<p>📽 Video version available on <a target="_blank" href="https://youtu.be/J2LYTHE3bUo">YouTube</a> and <a target="_blank" href="https://odysee.com/@svprdga:d/working-with-files-in-flutter">Odysee</a></p>
</blockquote>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/J2LYTHE3bUo">https://youtu.be/J2LYTHE3bUo</a></div>
<h3 id="heading-list-common-directories">List common directories</h3>
<p>By using the <a target="_blank" href="https://pub.dev/packages/path_provider">path_provider</a> plugin we can get the path to common directories designed for different purposes:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:path_provider/path_provider.dart'</span>;

<span class="hljs-comment">// Put cache files in this directory</span>
<span class="hljs-keyword">final</span> temporaryDirectory = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-comment">// For files that our app uses but are not exposed to the user</span>
<span class="hljs-keyword">final</span> appSupport = <span class="hljs-keyword">await</span> getApplicationSupportDirectory();
<span class="hljs-comment">// For user-generated files</span>
<span class="hljs-keyword">final</span> appDocuments = <span class="hljs-keyword">await</span> getApplicationDocumentsDirectory();
</code></pre>
<h3 id="heading-create-a-file">Create a file</h3>
<p>Let's create a random file in the temporary directory and write something inside:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Directory tempDir = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-keyword">final</span> File file = File(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/sample_file.txt'</span>);

<span class="hljs-keyword">await</span> file.writeAsString(<span class="hljs-string">'Sample content to write'</span>);
</code></pre>
<h3 id="heading-delete-a-file">Delete a file</h3>
<p>The delete method is just as crucial as the create method, as it helps clear out old files and thus saves valuable storage space.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Directory tempDir = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-keyword">final</span> File file = File(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/sample_file.txt'</span>);

<span class="hljs-keyword">await</span> file.delete();
</code></pre>
<h3 id="heading-create-a-directory">Create a directory</h3>
<p>Now let's say that I want to create a new file, but within a certain directory:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Directory tempDir = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-keyword">final</span> Directory newDirectory =
    Directory(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/sample_directory'</span>);

<span class="hljs-comment">// Always check that the directory exists</span>
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">await</span> newDirectory.exists() == <span class="hljs-keyword">false</span>) {
  <span class="hljs-keyword">await</span> newDirectory.create();
}

<span class="hljs-keyword">final</span> File file = File(<span class="hljs-string">'<span class="hljs-subst">${newDirectory.path}</span>/sample_file.txt'</span>);
<span class="hljs-keyword">await</span> file.writeAsString(<span class="hljs-string">'Sample content to write'</span>);
</code></pre>
<h3 id="heading-remove-a-directory">Remove a directory</h3>
<p>Now let's do the reverse and delete the directory:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Directory tempDir = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-keyword">final</span> Directory newDirectory =
    Directory(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/sample_directory'</span>);

<span class="hljs-keyword">await</span> newDirectory.delete(recursive: <span class="hljs-keyword">true</span>);
</code></pre>
<h3 id="heading-list-files">List files</h3>
<p>Sometimes you need to list all files within a directory to get some of its stats:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Directory directory = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;FileSystemEntity&gt; files = directory.listSync();

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">final</span> FileSystemEntity file <span class="hljs-keyword">in</span> files) {
  <span class="hljs-keyword">final</span> FileStat fileStat = <span class="hljs-keyword">await</span> file.stat();
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Path: <span class="hljs-subst">${file.path}</span>'</span>);
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Type: <span class="hljs-subst">${fileStat.type}</span>'</span>);
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Changed: <span class="hljs-subst">${fileStat.changed}</span>'</span>);
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Modified: <span class="hljs-subst">${fileStat.modified}</span>'</span>);
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Accessed: <span class="hljs-subst">${fileStat.accessed}</span>'</span>);
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Mode: <span class="hljs-subst">${fileStat.mode}</span>'</span>);
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Size: <span class="hljs-subst">${fileStat.size}</span>'</span>);
}
</code></pre>
<h3 id="heading-read-file">Read file</h3>
<p>Let's open a file to see its content:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Directory tempDir = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-keyword">final</span> File file = File(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/sample_file.txt'</span>);

<span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> fileContent = <span class="hljs-keyword">await</span> file.readAsString();
</code></pre>
<h3 id="heading-copy-file">Copy file</h3>
<p>Now, let's generate a duplicate of the previously created sample file:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Directory tempDir = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-keyword">final</span> File file = File(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/sample_file.txt'</span>);

<span class="hljs-keyword">final</span> File copy = <span class="hljs-keyword">await</span> file.copy(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/copy_file.txt'</span>);
</code></pre>
<h3 id="heading-rename-file">Rename file</h3>
<p>Next, let's change the name of the file we just copied:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Directory tempDir = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-keyword">final</span> File file = File(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/copy_file.txt'</span>);

<span class="hljs-keyword">await</span> file.rename(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/new_name.txt'</span>);
</code></pre>
<h3 id="heading-synchronously-manage-file-operations">Synchronously manage file operations</h3>
<p>So far, I've demonstrated how to handle files asynchronously, which is the preferred method. However, if for some reason Futures aren't an option for you, synchronous file operations are also possible:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Directory tempDir = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-keyword">final</span> File file = File(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/sample_file.txt'</span>);

file.writeAsStringSync(<span class="hljs-string">'New content to add'</span>);
</code></pre>
<h3 id="heading-error-handling">Error handling</h3>
<p>All file system operations should be performed safely since they are prone to throw exceptions. For simplicity, the previous examples did not include this, but it's critical to always encapsulate your I/O operations within a <code>try-catch</code> block:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> Directory tempDir = <span class="hljs-keyword">await</span> getTemporaryDirectory();
<span class="hljs-keyword">final</span> File file = File(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/sample_file.txt'</span>);

<span class="hljs-keyword">try</span> {
  <span class="hljs-keyword">await</span> file.writeAsString(<span class="hljs-string">'New content to add'</span>);
} <span class="hljs-keyword">catch</span> (e) {
  <span class="hljs-comment">// Handle IO error</span>
}
</code></pre>
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p>Dart and Flutter simplify working with files, as demonstrated above. I hope this summary was useful and clear.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Why you should use Flutter's compute() method for intensive tasks]]></title><description><![CDATA[📽 Video version available on YouTube and Odysee

Let's say you have a method that does the following:

Downloads an image from the internet

Create several copies of the image but resize them

Shows the original image and the copies on the screen


...]]></description><link>https://davidserrano.io/why-you-should-use-flutter-compute-method-for-intensive-tasks</link><guid isPermaLink="true">https://davidserrano.io/why-you-should-use-flutter-compute-method-for-intensive-tasks</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Flutter Examples]]></category><category><![CDATA[multithreading]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Thu, 29 Jun 2023 10:16:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688032903650/8473d152-b7b8-4440-b93e-782177575722.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>📽 Video version available on <a target="_blank" href="https://youtu.be/hWGVlyi5Grg">YouTube</a> and <a target="_blank" href="https://odysee.com/@svprdga:d/why-you-should-use-flutter-compute-method-for-intensive-tasks">Odysee</a></p>
</blockquote>
<p>Let's say you have a method that does the following:</p>
<ul>
<li><p>Downloads an image from the internet</p>
</li>
<li><p>Create several copies of the image but resize them</p>
</li>
<li><p>Shows the original image and the copies on the screen</p>
</li>
</ul>
<p>Specifically, it would be a method like this:</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-built_in">List</span>&lt;File&gt;&gt; _createImages(<span class="hljs-built_in">String</span> url, <span class="hljs-built_in">int</span> count) <span class="hljs-keyword">async</span> {
  <span class="hljs-comment">// Downloads the image</span>
  <span class="hljs-keyword">final</span> response = <span class="hljs-keyword">await</span> http.<span class="hljs-keyword">get</span>(<span class="hljs-built_in">Uri</span>.parse(url));
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;File&gt; files = [];

  <span class="hljs-comment">// Save the image in the cache directory</span>
  Directory tempDir = <span class="hljs-keyword">await</span> getTemporaryDirectory();
  File file = <span class="hljs-keyword">await</span> File(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/image.jpg'</span>)
      .writeAsBytes(response.bodyBytes);
  img.Image? image = img.decodeImage(file.readAsBytesSync());

  <span class="hljs-comment">// Generate copies with 50% less size than the previous</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; count; i++) {
    <span class="hljs-keyword">if</span> (image == <span class="hljs-keyword">null</span>) <span class="hljs-keyword">continue</span>;

    img.Image resizedImage = img.copyResize(image,
        width: (image.width * <span class="hljs-number">0.5</span>).round(),
        height: (image.height * <span class="hljs-number">0.5</span>).round());
    <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">int</span>&gt; resizedImageData = img.encodeJpg(resizedImage);

    file = <span class="hljs-keyword">await</span> File(<span class="hljs-string">'<span class="hljs-subst">${tempDir.path}</span>/image<span class="hljs-subst">$i</span>.jpg'</span>)
        .writeAsBytes(resizedImageData);
    files.add(file);

    image = img.decodeImage(file.readAsBytesSync());
  }

  <span class="hljs-comment">// Return the generated files paths</span>
  <span class="hljs-keyword">return</span> files;
}
</code></pre>
<p>For this to work, this method is called within a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html">FutureBuilder</a>, and once it gets the result it displays it as follows:</p>
<pre><code class="lang-dart">FutureBuilder&lt;<span class="hljs-built_in">List</span>&lt;File&gt;&gt;(
  future: _createImages(_url, <span class="hljs-number">4</span>),
  builder: (context, snapshot) {
    <span class="hljs-keyword">if</span> (snapshot.hasData) {
      <span class="hljs-comment">// Show all the images within a list</span>
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">// Show a loading while processing the images</span>
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> Center(
        child: CircularProgressIndicator(),
      );
    }
  },
)
</code></pre>
<p>If you attempt to execute this code, you'll notice the user interface becomes sluggish, possibly even unresponsive. Additionally, the frame rate experiences a significant decrease:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687969162736/ef79009b-4caa-491f-8416-c33a3c545942.png" alt class="image--center mx-auto" /></p>
<p>The reason for this is that even though Flutter provides us with a mechanism to execute asynchronous code via Futures, both synchronous and asynchronous code is actually executed on the same thread. Taking into account that working with images is expensive, part of the CPU load in carrying out these processes makes the fluidity of the application suffer, resulting in the image freezing for a few seconds until the image processing is over.</p>
<h3 id="heading-how-to-move-intensive-computation-to-a-flutter-background-thread-with-compute">How to move intensive computation to a Flutter background thread with compute()</h3>
<p>The <a target="_blank" href="https://api.flutter.dev/flutter/foundation/compute.html">compute</a>() method is intended to make it easier to work with background threads in Flutter. Specifically in the context of Flutter, these threads are called Isolates, and although we can call and use them in different ways, in today's example we will see how the Flutter API handles all these tasks when working with <em>compute()</em>.</p>
<p>First, we are going to modify the previous method that downloads the image so that it can work correctly in a background thread. Specifically, we will create a class that represents its parameters, we will also have to pass the temporary directory as a parameter, since background threads have problems when executing platform code:</p>
<pre><code class="lang-dart"><span class="hljs-comment">// Create a class that bundles the parameters</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateImagesParams</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> url;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> count;
  <span class="hljs-keyword">final</span> Directory directory;

  CreateImagesParams({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.url,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.count,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.directory,
  });
}

<span class="hljs-comment">// This MUST be a top-level function now</span>
Future&lt;<span class="hljs-built_in">List</span>&lt;File&gt;&gt; createImages(CreateImagesParams params) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> response = <span class="hljs-keyword">await</span> http.<span class="hljs-keyword">get</span>(<span class="hljs-built_in">Uri</span>.parse(params.url));
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;File&gt; files = [];

  File file = <span class="hljs-keyword">await</span> File(<span class="hljs-string">'<span class="hljs-subst">${params.directory.path}</span>/image.jpg'</span>)
      .writeAsBytes(response.bodyBytes);
  img.Image? image = img.decodeImage(file.readAsBytesSync());

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; params.count; i++) {
    <span class="hljs-keyword">if</span> (image == <span class="hljs-keyword">null</span>) <span class="hljs-keyword">continue</span>;

    img.Image resizedImage = img.copyResize(image,
        width: (image.width * <span class="hljs-number">0.5</span>).round(),
        height: (image.height * <span class="hljs-number">0.5</span>).round());
    <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">int</span>&gt; resizedImageData = img.encodeJpg(resizedImage);

    file = <span class="hljs-keyword">await</span> File(<span class="hljs-string">'<span class="hljs-subst">${params.directory.path}</span>/image<span class="hljs-subst">$i</span>.jpg'</span>)
        .writeAsBytes(resizedImageData);
    files.add(file);

    image = img.decodeImage(file.readAsBytesSync());
  }

  <span class="hljs-keyword">return</span> files;
}
</code></pre>
<p>Isolates have a completely separate memory space from other threads, so they cannot access instances created from another thread. For that reason, we will have to create a top-level function (a <em>static</em> function would also work).</p>
<p>We will also make some modifications to the rest of the widget:</p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
Widget build(BuildContext context) {
  <span class="hljs-keyword">return</span> Scaffold(
    appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'WITH Compute'</span>)),
    body: FutureBuilder&lt;<span class="hljs-built_in">List</span>&lt;File&gt;&gt;(
      future: _createImages(),
      builder: (context, snapshot) {
        <span class="hljs-keyword">if</span> (snapshot.hasData) {
          <span class="hljs-comment">// Show all the images within a list</span>
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    ),
  );
}

Future&lt;<span class="hljs-built_in">List</span>&lt;File&gt;&gt; _createImages() <span class="hljs-keyword">async</span> {
  <span class="hljs-comment">// We call compute() with a reference to the top-level</span>
  <span class="hljs-comment">// function, as well as an instance of its parameters</span>
  <span class="hljs-keyword">return</span> compute(
    createImages,
    CreateImagesParams(
      url: _url,
      count: <span class="hljs-number">4</span>,
      directory: <span class="hljs-keyword">await</span> getTemporaryDirectory(),
    ),
  );
}
</code></pre>
<p>If you try to execute this code now, despite the processing time for all the images being approximately the same, you will notice that the UI does not freeze. The loading widget we have implemented continues to spin throughout. This is because we have shifted the resource-intensive task to a background thread, allowing the main thread to maintain a smooth interface movement.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Flutter incorporates different ways of working with threads. As I have already commented in the article, these are called Isolates and they can be called directly, controlling each of the steps of their life cycle. However for simple tasks that only run for a few seconds and then complete it is better to use the <em>compute()</em> method, since it abstracts us from the relevant management of those threads.</p>
<p>You can find the complete project shown in this article <a target="_blank" href="https://github.com/svprdga/Flutter-Compute-Sample">here</a>.</p>
<p>I hope you have found this article useful, see you at the next one!</p>
]]></content:encoded></item><item><title><![CDATA[Add a Video Conference feature to your Flutter App]]></title><description><![CDATA[In this article, I will explain how to quickly and easily add video conferencing functionality to your application.
This kind of technology usually involves a significant amount of work behind it. Not only do you need to understand how to implement a...]]></description><link>https://davidserrano.io/add-a-video-conference-feature-to-your-flutter-app</link><guid isPermaLink="true">https://davidserrano.io/add-a-video-conference-feature-to-your-flutter-app</guid><category><![CDATA[Flutter]]></category><category><![CDATA[video streaming]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Fri, 09 Jun 2023 08:46:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1686218470316/4669dd28-03fe-41d2-bcd7-4133fafeb51a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, I will explain how to quickly and easily add video conferencing functionality to your application.</p>
<p>This kind of technology usually involves a significant amount of work behind it. Not only do you need to understand how to implement and manage costly audio and video servers along with their respective video streaming technologies, but you also need to know how to properly integrate it into your frontend app, all while keeping latency to a minimum.</p>
<p>For this reason, <a target="_blank" href="https://www.zegocloud.com/">ZEGOCLOUD</a>, a company I've been working with for some time, provides state-of-the-art solutions in this field, offering you these capabilities without the need to worry about infrastructure costs or dedicating effort to integrate them into your app. With their UIKit, you can add another widget to your Flutter application that will automatically connect to your system and enable you to incorporate video conferencing functionality into your app in just a few minutes.</p>
<p>To do this, I'm going to show you the steps one by one that you need to follow to accomplish it. The first step is to visit the <a target="_blank" href="https://console.zegocloud.com/account/signup">official ZEGOCLOUD website and sign up</a>. Everything I'm going to explain here can be tried with a free account, <strong>without the need to add any payment method</strong>. This way, you can evaluate the service and see it in action without any cost.</p>
<h2 id="heading-create-the-project-in-the-zegocloud-dashboard">Create the project in the ZEGOCLOUD Dashboard</h2>
<p>The first thing you should do is create a project. You can do it by clicking on the <em>"Create"</em> button in the upper right corner, in the <em>"Projects"</em> section:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686215454881/727dc8f0-0013-4dcb-b011-b3e88188ce58.png" alt="Create a project" class="image--center mx-auto" /></p>
<p>Next, you will be prompted to select the use case for your project. As you can see, there are numerous options to choose from; in this instance, we will select <em>"Video Conference"</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686215491507/484b3444-ab72-499c-b61d-2cc0affba0a0.png" alt="Select Video Conference use-case" class="image--center mx-auto" /></p>
<p>Let's give the project a name:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686215509366/477f9670-5554-42f4-b3db-f25461f97ba5.png" alt="Add a name to the project" class="image--center mx-auto" /></p>
<p>You have two options: one is to use their SDK for a fully custom implementation, where you can decide exactly how to interact with their services. The other option is to use a <a target="_blank" href="https://www.zegocloud.com/uikits"><strong>UIKit</strong></a>, which allows you to add a widget and have all the functionality implemented, including the visual interface.</p>
<p>Since we want to add this functionality as quickly as possible, we'll choose the <em>"Start with UIKits"</em> option:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686215570062/868e7813-c427-426e-ba48-35d4827701f3.png" alt="Start with UIKits" class="image--center mx-auto" /></p>
<p>Then we can choose the platform on which we want to work, for this tutorial we will do it on a Flutter application:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686215587431/bc95ead4-16cc-4db3-b476-1bb31400f867.png" alt class="image--center mx-auto" /></p>
<p>Everything is ready, press the <em>"Save &amp; Start to integrate"</em> button and your project will be ready to be implemented.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686215600490/2fdfb149-8be5-4c8a-bc61-f83d450c9b12.png" alt class="image--center mx-auto" /></p>
<p>Now you can view your project's configuration. You'll see your <strong>AppID</strong> and <strong>AppSign</strong>. Save these values, as you'll need them later to connect your app to the project you just created.</p>
<h2 id="heading-add-a-video-conference-feature-to-your-flutter-app">Add a Video Conference feature to your Flutter app</h2>
<p>Now the fun part! Create a new Flutter application and add the following dependencies:</p>
<pre><code class="lang-bash">flutter create --platform android,ios flutter_video_conference
<span class="hljs-built_in">cd</span> flutter_video_conference
flutter pub add zego_uikit_prebuilt_video_conference
flutter pub add uuid
</code></pre>
<p>The <a target="_blank" href="https://pub.dev/packages/zego_uikit_prebuilt_video_conference">zego_uikit_prebuilt_video_conference</a> plugin incorporates the UIKit for adding the Video Conference feature. We'll also add <a target="_blank" href="https://pub.dev/packages/uuid">uuid</a> to generate random identifiers for this example.</p>
<p>Before start developing, we must do some setup. Start by changing the following variables in <code>android/app/build.gradle</code>:</p>
<pre><code class="lang-bash">compileSdkVersion 33
minSdkVersion 22
targetSdkVersion 33
</code></pre>
<p>To add this feature, we must add the following permissions to <code>android/app/src/main/AndroidManifest.xml</code>:</p>
<pre><code class="lang-bash">&lt;uses-permission android:name=<span class="hljs-string">"android.permission.ACCESS_WIFI_STATE"</span> /&gt;
&lt;uses-permission android:name=<span class="hljs-string">"android.permission.RECORD_AUDIO"</span> /&gt;
&lt;uses-permission android:name=<span class="hljs-string">"android.permission.INTERNET"</span> /&gt;
&lt;uses-permission android:name=<span class="hljs-string">"android.permission.ACCESS_NETWORK_STATE"</span> /&gt;
&lt;uses-permission android:name=<span class="hljs-string">"android.permission.CAMERA"</span> /&gt;
&lt;uses-permission android:name=<span class="hljs-string">"android.permission.BLUETOOTH"</span> /&gt;
&lt;uses-permission android:name=<span class="hljs-string">"android.permission.MODIFY_AUDIO_SETTINGS"</span> /&gt;
&lt;uses-permission android:name=<span class="hljs-string">"android.permission.WRITE_EXTERNAL_STORAGE"</span> /&gt;
&lt;uses-permission android:name=<span class="hljs-string">"android.permission.READ_PHONE_STATE"</span> /&gt;
&lt;uses-permission android:name=<span class="hljs-string">"android.permission.WAKE_LOCK"</span> /&gt;
</code></pre>
<p>We should do the same for iOS. Open <code>ios/Podfile</code> and modify the last part of the file with the following addition:</p>
<pre><code class="lang-bash">post_install <span class="hljs-keyword">do</span> |installer|
  installer.pods_project.targets.each <span class="hljs-keyword">do</span> |target|
    flutter_additional_ios_build_settings(target)

    <span class="hljs-comment"># Add this block</span>
    target.build_configurations.each <span class="hljs-keyword">do</span> |config|
      config.build_settings[<span class="hljs-string">'GCC_PREPROCESSOR_DEFINITIONS'</span>] ||= [
        <span class="hljs-string">'$(inherited)'</span>,
        <span class="hljs-string">'PERMISSION_CAMERA=1'</span>,
        <span class="hljs-string">'PERMISSION_MICROPHONE=1'</span>,
      ]
    end
  end
end
</code></pre>
<p>Declare the permissions in <code>ios/Info.plist</code>:</p>
<pre><code class="lang-bash">&lt;key&gt;NSCameraUsageDescription&lt;/key&gt;
&lt;string&gt;Camera access is required to create a Video Conference&lt;/string&gt;
&lt;key&gt;NSMicrophoneUsageDescription&lt;/key&gt;
&lt;string&gt;Microphone access is required to create a Video Conference&lt;/string&gt;
</code></pre>
<p>Great! Now everything is ready to start developing. Copy and paste the following Dart code in your <code>lib/main.dart</code> file. I've added several comments to the code so you can fully understand what I'm doing and how you can extend it further if you want:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:uuid/uuid.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:zego_uikit_prebuilt_video_conference/zego_uikit_prebuilt_video_conference.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  runApp(<span class="hljs-keyword">const</span> App());
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">App</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> App({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'Flutter Video Conference'</span>,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: <span class="hljs-keyword">true</span>,
      ),
      home: <span class="hljs-keyword">const</span> MainScreen(),
    );
  }
}

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

  <span class="hljs-meta">@override</span>
  State&lt;MainScreen&gt; createState() =&gt; _MainScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MainScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">MainScreen</span>&gt; </span>{
  <span class="hljs-comment">// This is the ID of this conference. We'll use this harcoded value</span>
  <span class="hljs-comment">// for now, but you should use a custom ID managed by your system</span>
  <span class="hljs-comment">// in order to manage all conferences.</span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> _conferenceId = <span class="hljs-string">'my_conference'</span>;

  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> _userId;
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> _userName;

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();

    <span class="hljs-comment">// We will create a random ID for now. But you should</span>
    <span class="hljs-comment">// use the ID that you already use for your users, </span>
    <span class="hljs-comment">// and the username that each of them have.</span>
    _userId = <span class="hljs-keyword">const</span> Uuid().v4();
    _userName = <span class="hljs-string">'user_<span class="hljs-subst">${_userId.substring(<span class="hljs-number">0</span>, <span class="hljs-number">7</span>)}</span>'</span>;
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> SafeArea(
      <span class="hljs-comment">// You just need to add this widget in order to</span>
      <span class="hljs-comment">// add the functionality to your app.</span>
      child: ZegoUIKitPrebuiltVideoConference(
        <span class="hljs-comment">// Add the AppID and AppSign that you saw before in</span>
        <span class="hljs-comment">// the project configuration in the Dashboard</span>
        appID: your_app_id,
        appSign:
            <span class="hljs-string">'your_app_sign'</span>,
        userID: _userId,
        userName: _userName,
        conferenceID: _conferenceId,
        config: ZegoUIKitPrebuiltVideoConferenceConfig(),
      ),
    );
  }
}
</code></pre>
<p>Execute this code on two devices if possible. You will observe that you can engage in a video conference with your colleagues.</p>
<p>If you are looking for deeper requirements with video call SDK, ZEGOCLOUD also has a fully customized <a target="_blank" href="https://www.zegocloud.com/product/video-call">video calling SDK</a> to meet your requirements.</p>
<p>And just like that you can have this feature implemented without the hassle of managing servers. Thanks to ZEGOCLOUD for sponsoring this article, and don't hesitate to go to <a target="_blank" href="https://www.zegocloud.com/">their website</a> to see more information about their services.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[A Summary of WWDC 2023's New Developer APIs]]></title><description><![CDATA[The WWDC 2023 keynote just wrapped up, and Apple announced several new products and updates related to app development for iOS, iPad, macOS, and the rest of the operating systems of the company.
The event began with the unveiling of the new 15-inch M...]]></description><link>https://davidserrano.io/a-summary-of-wwdc-2023s-new-developer-apis</link><guid isPermaLink="true">https://davidserrano.io/a-summary-of-wwdc-2023s-new-developer-apis</guid><category><![CDATA[Apple]]></category><category><![CDATA[iOS]]></category><category><![CDATA[macOS]]></category><category><![CDATA[APIs]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Tue, 06 Jun 2023 15:58:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1686089452019/3736d18e-e83b-4cb2-9845-ef1aae427d16.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The WWDC 2023 keynote just wrapped up, and Apple announced several new products and updates related to app development for iOS, iPad, macOS, and the rest of the operating systems of the company.</p>
<p>The event began with the unveiling of the new 15-inch MacBook Air, followed by the Mac Studio upgrade featuring the M2 Max and M2 Ultra chips, and concluded with the announcement of the new Mac Pro powered by the M2 Ultra chip. Additionally, they introduced a new product, the Vision Pro, which are augmented reality glasses that will surely give a lot to talk about in the coming weeks.</p>
<p>However, this blog focuses on software, so in this article, I will provide a summary centered on software news for developers, including new APIs and the most notable changes announced that can potentially open new ways to develop new features for your apps.</p>
<blockquote>
<p>📽 Video version available on <a target="_blank" href="https://youtu.be/_liThcM4_HY">YouTube</a> and <a target="_blank" href="https://odysee.com/@svprdga:d/a-summary-of-wwdc-2023s-new-developer-apis:7">Odysee</a></p>
</blockquote>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/_liThcM4_HY">https://youtu.be/_liThcM4_HY</a></div>
<p> </p>
<h2 id="heading-posters-visual-identities-for-your-contacts">Posters: visual identities for your contacts</h2>
<p>We begin with the new major release of the OS that powers the iPhone: iOS 17. The first new feature announced is the addition of the Posters functionality for the calling app. This feature allows us to customize the interface when we receive a call from a contact. It lets us set the image and adjust the text, similar to how the wallpaper is customized. This configuration is also used in the contacts app, to maintain the same design whether we are viewing a contact's information or initiating a call with them. We can use this feature through the CallKit API, for instance, in a Voice over IP (VoIP) application, by displaying the image previously assigned by the user for a specific contact when they make a call.</p>
<p>With this new feature, users can assign visual identities to their contacts, which will be consistently displayed across system applications and third-party apps alike, enhancing their overall experience.</p>
<h2 id="heading-shareplay-bring-your-device-closer-and-share">SharePlay: bring your device closer and share</h2>
<p>The next feature is the possibility to share content via the SharePlay API with the simple gesture of bringing your device closer to another. In the presentation, Apple demonstrates how it can be used to quickly share your contact information with another person, or how to send images or videos. With SharePlay, we can implement sharing functions that work simply by bringing one device closer to another. For example, if we want to recommend a YouTube video to a friend, it would be enough to have it open and bring it closer to their phone, or imagine wanting to share an article from a news app or social network with another person. Through this new API, we can carry out any option to share data or content with another user much faster and with fewer actions, with the simple gesture of bringing the devices closer.</p>
<h2 id="heading-the-new-suggestions-api">The new suggestions API</h2>
<p>In this presentation, Apple has announced a new app that we will see from iOS 17: Journal. With this app, we will be able to maintain a personal diary in which we can record anything we want about our lives. What's notable here is that in the presentation, it is mentioned that this app uses the new Suggestions API, which allows for a series of suggestions based on other user data, such as locations or photos, to suggest things to write about. Developers will also have access to this API, and I imagine it can be used in a similar way, I mean: I assume that through this API, we can leverage user-generated content to provide suggestions based on their previous inputs. It's not entirely clear to me how it will be used, and I have my doubts about the privacy that this API will be able to maintain, but we will see all of this as soon as we start working with this new API.</p>
<h2 id="heading-healthkit-for-ipad-17">HealthKit for iPad 17</h2>
<p>We move on to the operating system that powers the iPads: the new iPadOS 17. One of the innovations introduced is the ability to use the HealthKit API on these tablets. Just like in iOS, through this API we can interact with data related to the user's health and fitness. We can collect, analyze, and process this data, for instance in apps related to weight tracking, step monitoring, health apps, or sports apps.</p>
<h2 id="heading-create-webapps-from-safari">Create WebApps from Safari</h2>
<p>In this presentation, Apple also unveiled the next major release of its desktop operating system: macOS Sonoma. In this section, I will highlight a few new features that caught my attention in the new version of Safari.</p>
<p>Now any user can create WebApps from Safari. In this case, it's not something new that you as a developer can use, since you don't have to make any changes for it. Any user can decide to turn any webpage into an application that will integrate seamlessly with the rest of the applications installed on the Mac. The user can set its name, the image will be the main icon of the website and then it will automatically appear on the Dock. From then on, this website can be used as if it were another application installed on the system.</p>
<p>Another feature worth noting is that Safari finally catches up with other browsers by adding the ability to create profiles. This allows users to separate cookies, bookmarks, history, and other browsing data by creating different profiles. This feature can be very useful, for instance, to separate the accounts that you use for work from the accounts that you use for your personal projects.</p>
<h2 id="heading-continuity-camera-api">Continuity Camera API</h2>
<p>To finish this list of news, I'm going to talk about the possibility of using the Continuity Camera API in applications for tvOS. With the help of this API, developers can utilize the iPhone camera within a tvOS app, enabling it to function as a camera. If you happen to be a programmer working on this operating system and have eagerly awaited a solution to incorporate camera functionality, your wait is over. During the Apple presentation, they showcased the seamless integration of the iPhone camera into a FaceTime call using this very API.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>So, here you have a super quick rundown of the coolest updates I found for app development on Apple devices. You should check out the <a target="_blank" href="https://www.youtube.com/watch?v=GYkq9Rgoj8E">whole Keynote</a> to expand on this information and to find out about the rest of the news presented at WWDC 2023.</p>
<p>Until next time!</p>
]]></content:encoded></item><item><title><![CDATA[Build a Flutter chat system in under 5 minutes]]></title><description><![CDATA[Chat features are typically an essential element in many applications, enabling users to communicate with one another or with a company representative. This interaction often leads to increased satisfaction for the customer or user, as well as height...]]></description><link>https://davidserrano.io/build-a-flutter-chat-system-in-under-5-minutes</link><guid isPermaLink="true">https://davidserrano.io/build-a-flutter-chat-system-in-under-5-minutes</guid><category><![CDATA[Flutter]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Fri, 19 May 2023 09:17:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1684324769502/18559ef3-5417-41d3-af36-0623974afb19.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Chat features are typically an essential element in many applications, enabling users to communicate with one another or with a company representative. This interaction often leads to increased satisfaction for the customer or user, as well as heightened engagement with the app, since humans are inherently social beings and generally appreciate contact with others.</p>
<p>However, if you're thinking about <a target="_blank" href="https://www.zegocloud.com/product/in-app-chat">building a chat system for your app</a>, you've probably already recognized the vast array of features you'll need to develop, from designing one-on-one chats and <a target="_blank" href="https://www.zegocloud.com/blog/how-to-make-a-group-chat">group chat</a> architecture to user management, message sending and notifications, as well as considering privacy and security aspects, and so on.</p>
<p>For this reason, in this article, I provide you with a quick tutorial on how to add pre-built chat functionality to your application using <a target="_blank" href="https://www.zegocloud.com/">ZEGOCLOUD</a>, a company specializing in offering <a target="_blank" href="https://www.zegocloud.com/product/live-streaming">video and audio streaming services</a>, as well as chat services in this case. I've been working with this company for a few months now, and I know for a fact that using their SDK, it's possible to bypass all the complexity I mentioned earlier and build your chat system quickly and easily.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684324249831/6de10de1-34ae-45c7-988f-e8bb97dda35e.png" alt="With the ZEGOCLOUD SDK you can create a chat system quickly and easily." class="image--center mx-auto" /></p>
<h2 id="heading-how-to-add-chat-functionality-to-your-flutter-app">How to add Chat functionality to your Flutter app</h2>
<p>Let's start with the tutorial, first go to their <a target="_blank" href="https://console.zegocloud.com/account/signup">official page and sign up</a>. You can sign up and create a test project to test the service completely free of charge without entering any payment method. Once you have done it, click on <em>"Create your project"</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684317279388/7b378375-12e7-4894-b6e2-6c05747d94eb.png" alt="Create project" class="image--center mx-auto" /></p>
<p>Then we will have to select which is the use case of our project, we will select <em>"In-app Chat"</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684317352860/be4fcbf5-ccce-4222-87bb-92e774bec0c0.png" alt="In-app Chat use case" class="image--center mx-auto" /></p>
<p>Then we will simply have to give it a name, you can choose the one you want:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684317392399/af887165-063b-4406-ad1a-ccf333f7e054.png" alt="Set project name" class="image--center mx-auto" /></p>
<p>Once the project has been created, click on it to see the configuration information. On this screen you will see two values: <strong>AppId</strong> and <strong>AppSign</strong>. You will need to use these values later to connect your application to the project you just created.</p>
<p>Now go to <strong>Service Management</strong>, <strong>In-app Chat</strong> and activate the functionality:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684323541871/f016a210-0a95-4773-a857-fa17181bd27e.png" alt class="image--center mx-auto" /></p>
<p>Perfect. We already have everything ready. Next, I'm going to create a base app from which you could start building your chat system.</p>
<h2 id="heading-adding-chat-functionality-to-a-flutter-app">Adding chat functionality to a Flutter app</h2>
<p>Let's start by creating a new app for Android and iOS:</p>
<pre><code class="lang-bash">flutter create --platforms android,ios flutter_chat_app
</code></pre>
<p>For the chat functionality, we need to add the <a target="_blank" href="https://pub.dev/packages/zego_zim">zego_zim</a> dependency:</p>
<pre><code class="lang-bash">flutter pub add zego_zim
</code></pre>
<p>Next, you'll see a large block of code. If you run it, you'll be able to send messages from one device to another. You can find detailed explanations of each step in the form of comments:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:zego_zim/zego_zim.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  runApp(<span class="hljs-keyword">const</span> App());
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">App</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> App({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> MaterialApp(
      debugShowCheckedModeBanner: <span class="hljs-keyword">false</span>,
      title: <span class="hljs-string">'Flutter Chat App'</span>,
      home: ChatScreen(),
    );
  }
}

<span class="hljs-comment">/// <span class="markdown">This class will help us to represent the information</span></span>
<span class="hljs-comment">/// <span class="markdown">of the messages within our app.</span></span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Message</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> content;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> userId;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> userName;

  Message({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.content,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.userId,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.userName,
  });
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChatScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">const</span> ChatScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  State&lt;ChatScreen&gt; createState() =&gt; _ChatScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ChatScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ChatScreen</span>&gt; </span>{
  <span class="hljs-comment">// Make sure you protect these values correctly, ideally you should</span>
  <span class="hljs-comment">// get them from your secured backend after the user has logged in.</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> _appId = your_app_id;
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> _appSign =
      <span class="hljs-string">'your_app_sign'</span>;

  <span class="hljs-comment">// For this example we are going to hardcode the ID of user A</span>
  <span class="hljs-comment">// and user B. It is important that in your chat system you use</span>
  <span class="hljs-comment">// your own identifiers, for example the ones you are already</span>
  <span class="hljs-comment">// using to identify users.</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> _userAId = <span class="hljs-string">'userA1234'</span>;
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> _userBId = <span class="hljs-string">'userB1234'</span>;

  <span class="hljs-comment">// Set this variable to true on one device and false on</span>
  <span class="hljs-comment">// another to simulate different users.</span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">bool</span> _isUserA = <span class="hljs-keyword">true</span>;

  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> ZIM _zim;
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> ZIMAppConfig _appConfig;
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> ZIMUserInfo _userInfo;
  <span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> Future&lt;<span class="hljs-keyword">void</span>&gt; _future;

  <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;Message&gt; _messages = [];

  <span class="hljs-keyword">final</span> TextEditingController _controller = TextEditingController();
  <span class="hljs-keyword">final</span> FocusNode _focusNode = FocusNode();
  <span class="hljs-keyword">final</span> ScrollController _scrollController = ScrollController();

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();

    <span class="hljs-comment">// Initialize the ZIMAppConfig with your appId and appSign</span>
    _appConfig = ZIMAppConfig()
      ..appID = _appId
      ..appSign = _appSign;
    _zim = ZIM.create(_appConfig)!;

    <span class="hljs-keyword">final</span> id = _isUserA ? _userAId : _userBId;

    _userInfo = ZIMUserInfo()
      ..userID = id
      ..userName = <span class="hljs-string">'user_<span class="hljs-subst">$id</span>'</span>;

    <span class="hljs-comment">// For now we will use a FutureBuilder that will log the user.</span>
    _future = _initialize();
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> dispose() {
    _controller.dispose();
    _scrollController.dispose();
    _zim.logout();
    _zim.destroy();
    <span class="hljs-keyword">super</span>.dispose();
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) =&gt; FutureBuilder(
        future: _future,
        builder: (context, snapshot) {
          Widget bodyWidget;

          <span class="hljs-comment">//In case of error we show a message on the screen</span>
          <span class="hljs-keyword">if</span> (snapshot.hasError) {
            bodyWidget = Center(
              child: Container(
                padding: <span class="hljs-keyword">const</span> EdgeInsets.only(left: <span class="hljs-number">24.0</span>, right: <span class="hljs-number">24.0</span>),
                child: <span class="hljs-keyword">const</span> Text(
                  <span class="hljs-string">'An error occurred while trying to initialize the chat.'</span>,
                  textAlign: TextAlign.center,
                ),
              ),
            );
          } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (snapshot.connectionState == ConnectionState.done) {
            bodyWidget = Column(
              children: [
                <span class="hljs-comment">// Show the list of message</span>
                Expanded(
                  child: ListView(
                    controller: _scrollController,
                    children: [
                      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">final</span> message <span class="hljs-keyword">in</span> _messages)
                        Align(
                          alignment: message.userId == _userInfo.userID
                              ? Alignment.centerRight
                              : Alignment.centerLeft,
                          child: Container(
                            padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">8.0</span>),
                            child: _MessageEntry(
                              message: message,
                              isOwnMessage: message.userId == _userInfo.userID,
                            ),
                          ),
                        )
                    ],
                  ),
                ),
                <span class="hljs-comment">// Send a message</span>
                Container(
                  padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">12.0</span>),
                  color: Colors.grey[<span class="hljs-number">200</span>],
                  child: Row(
                    children: [
                      Expanded(
                        child: TextFormField(
                          controller: _controller,
                          focusNode: _focusNode,
                          decoration: <span class="hljs-keyword">const</span> InputDecoration(
                              hintText: <span class="hljs-string">'Write a message here'</span>),
                        ),
                      ),
                      ElevatedButton(
                        onPressed: () {
                          _focusNode.unfocus();
                          _sendMessage(_controller.text);
                          _controller.clear();
                        },
                        child: Row(
                          children: <span class="hljs-keyword">const</span> [
                            Text(<span class="hljs-string">'SEND'</span>),
                            Padding(
                                padding: EdgeInsets.only(left: <span class="hljs-number">4.0</span>),
                                child: Icon(Icons.send)),
                          ],
                        ),
                      ),
                    ],
                  ),
                )
              ],
            );
          } <span class="hljs-keyword">else</span> {
            bodyWidget = <span class="hljs-keyword">const</span> Center(
              child: CircularProgressIndicator(),
            );
          }

          <span class="hljs-keyword">return</span> Scaffold(
            appBar: AppBar(
              title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Flutter Chat App'</span>),
            ),
            body: bodyWidget,
          );
        },
      );

  <span class="hljs-comment">/// <span class="markdown">In this method we will login the user and we will also</span></span>
  <span class="hljs-comment">/// <span class="markdown">set up a listener to receive messages from the other user.</span></span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; _initialize() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _zim.login(_userInfo);

    ZIMEventHandler.onReceivePeerMessage =
        (ZIM zim, <span class="hljs-built_in">List</span>&lt;ZIMMessage&gt; messageList, <span class="hljs-built_in">String</span> fromUserID) {
      <span class="hljs-keyword">for</span> (ZIMMessage message <span class="hljs-keyword">in</span> messageList) {
        <span class="hljs-keyword">switch</span> (message.type) {
          <span class="hljs-keyword">case</span> ZIMMessageType.text:
            message <span class="hljs-keyword">as</span> ZIMTextMessage;
            _addMessage(
              Message(
                  content: message.message,
                  userId: message.senderUserID,
                  userName: <span class="hljs-string">'user_<span class="hljs-subst">${message.senderUserID}</span>'</span>),
            );
            <span class="hljs-keyword">break</span>;
          <span class="hljs-keyword">case</span> ZIMMessageType.command:
            message <span class="hljs-keyword">as</span> ZIMCommandMessage;
            <span class="hljs-keyword">break</span>;
          <span class="hljs-keyword">case</span> ZIMMessageType.image:
            message <span class="hljs-keyword">as</span> ZIMImageMessage;
            <span class="hljs-keyword">break</span>;
          <span class="hljs-keyword">case</span> ZIMMessageType.file:
            message <span class="hljs-keyword">as</span> ZIMFileMessage;
            <span class="hljs-keyword">break</span>;
          <span class="hljs-keyword">default</span>:
        }
      }
    };
  }

  <span class="hljs-comment">/// <span class="markdown">This method will send a message to the other user.</span></span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; _sendMessage(<span class="hljs-built_in">String</span> message) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> scaffoldMessenger = ScaffoldMessenger.of(context);

    <span class="hljs-keyword">final</span> textMessage = ZIMTextMessage(message: message);
    <span class="hljs-keyword">final</span> sendConfig = ZIMMessageSendConfig()
      ..priority = ZIMMessagePriority.low;

    <span class="hljs-keyword">final</span> pushConfig = ZIMPushConfig();
    pushConfig.title = <span class="hljs-string">"New message"</span>;
    pushConfig.content = message;

    sendConfig.pushConfig = pushConfig;

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> _zim.sendMessage(
        textMessage,
        _isUserA ? _userBId : _userAId,
        ZIMConversationType.peer,
        sendConfig,
      );
      _addMessage(Message(
        content: message,
        userId: _userInfo.userID,
        userName: _userInfo.userName,
      ));
    } <span class="hljs-keyword">on</span> Exception <span class="hljs-keyword">catch</span> (_) {
      scaffoldMessenger.showSnackBar(
        <span class="hljs-keyword">const</span> SnackBar(
          content: Text(
            <span class="hljs-string">'An error occurred while trying to send a message'</span>,
          ),
        ),
      );
    }
  }

  <span class="hljs-keyword">void</span> _addMessage(Message message) {
    setState(() {
      _messages.add(message);
      _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
    });
  }
}

<span class="hljs-comment">/// <span class="markdown">This widget will be the visual representation of</span></span>
<span class="hljs-comment">/// <span class="markdown">each message within the message list.</span></span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MessageEntry</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> Message message;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">bool</span> isOwnMessage;

  <span class="hljs-keyword">const</span> _MessageEntry({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.message, <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.isOwnMessage});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) =&gt; SizedBox(
        width: <span class="hljs-number">200.0</span>,
        child: Container(
          padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">8.0</span>),
          decoration: BoxDecoration(
            borderRadius: <span class="hljs-keyword">const</span> BorderRadius.all(Radius.circular(<span class="hljs-number">8.0</span>)),
            color: isOwnMessage ? Colors.blue[<span class="hljs-number">800</span>] : Colors.blue,
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                message.userName,
                style: <span class="hljs-keyword">const</span> TextStyle(
                  color: Colors.white,
                  fontSize: <span class="hljs-number">10.0</span>,
                  fontWeight: FontWeight.bold,
                ),
              ),
              <span class="hljs-keyword">const</span> Divider(
                color: Colors.white54,
              ),
              Text(
                message.content,
                style: <span class="hljs-keyword">const</span> TextStyle(color: Colors.white),
              ),
            ],
          ),
        ),
      );
}
</code></pre>
<p>And there you have it! This straightforward tutorial has demonstrated how to incorporate chat functionality into your Flutter application by utilizing the powerful <a target="_blank" href="https://www.zegocloud.com/">ZEGOCLOUD SDK</a>. I sincerely hope that you found this guide to be informative and beneficial, as it aims to provide you with a solid foundation for swiftly implementing a chat system within your app.</p>
<p>I wish you the best of luck in your future endeavors, and I look forward to sharing more valuable insights with you in the future. Until next time, happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Impeller is PROD ready for iOS, Dart 3, x3 WEB Performance and more with Flutter 3.10]]></title><description><![CDATA[Flutter 3.10 is finally here and it's packed with incredible new features! 🎉 Get ready for the game-changing release of Impeller for iOS, the revolutionary Dart 3 that makes null-safe code mandatory, and a whole bunch of Web enhancements, including ...]]></description><link>https://davidserrano.io/impeller-is-production-ready-for-ios-dart-3-x3-web-performance-and-more-with-flutter-3-10</link><guid isPermaLink="true">https://davidserrano.io/impeller-is-production-ready-for-ios-dart-3-x3-web-performance-and-more-with-flutter-3-10</guid><category><![CDATA[Flutter]]></category><dc:creator><![CDATA[David Serrano]]></dc:creator><pubDate>Thu, 11 May 2023 16:08:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683820835585/c74f8386-3a1d-4fd4-99ff-b2e1cfb836a6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Flutter 3.10 is finally here and it's packed with incredible new features! 🎉 Get ready for the game-changing release of Impeller for iOS, the revolutionary Dart 3 that makes null-safe code mandatory, and a whole bunch of Web enhancements, including the cutting-edge WASM standard that will make your Flutter apps run up to 3 times faster on the web! Plus, we're going to see more improvements to the Material 3 widgets, enhancements in the DevTools for easier app profiling, and support for SLSA level 1 to tackle potential security risks.</p>
<p>Stick with me as I'm going to give you a hyper-quick tour of all these improvements so you can get an overview of the best of the best in Flutter 3.10.</p>
<blockquote>
<p>📽 Video version available on <a target="_blank" href="https://youtu.be/xaGe8ptTfZM">YouTube</a> and <a target="_blank" href="https://odysee.com/@svprdga:d/impeller-is-production-ready-for-ios-dart-3-x3-web-performance-and-more-with-flutter-3-10">Odysee</a></p>
</blockquote>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/xaGe8ptTfZM">https://youtu.be/xaGe8ptTfZM</a></div>
<p> </p>
<h3 id="heading-impeller-is-production-ready-on-ios">Impeller is production-ready on iOS</h3>
<p>In Flutter 3.10, Impeller replaces Skia and becomes the primary rendering engine on iOS. This new rendering engine brings improved performance to our animations and eliminates the troublesome shader compilation issues that caused janky animations and an unpleasant visual experience. To achieve this, Impeller utilizes a tessellation algorithm that bypasses the need for shader compilation during graphics rendering.</p>
<p>Impeller harnesses the advanced capabilities of next-generation GPUs to render various shapes and colors on the screen seamlessly, ensuring a consistently high framerate. It has been meticulously crafted from the ground up to cater specifically to the requirements of Flutter.</p>
<p>In Flutter 3.10, Impeller is the default rendering engine for all iOS apps developed with Flutter. Furthermore, the Flutter team has revealed their ongoing efforts to bring Impeller to Android, with a preview version slated for future releases.</p>
<h3 id="heading-dart-3">Dart 3</h3>
<p>Flutter 3.10 introduces an exciting addition: Dart 3, the latest major release of the programming language driving Flutter. Dart 3 brings a significant enhancement by completely removing non-null-safe code, ensuring a fully protected and problem-free experience in this 100% safe language, eliminating the pitfalls typically associated with nullable languages.</p>
<p>Furthermore, Dart 3 introduces numerous language enhancements, including the introduction of Patterns. This feature makes it easy to work with structured data. The official <a target="_blank" href="https://medium.com/dartlang/announcing-dart-3-53f065a10635">Dart 3 blog post</a> provides a practical demonstration of a function that conveniently returns two values simultaneously.</p>
<pre><code class="lang-dart">(<span class="hljs-built_in">String</span>, <span class="hljs-built_in">int</span>) userInfo(<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; json) {
  <span class="hljs-keyword">return</span> (json[<span class="hljs-string">'name'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">String</span>, json[<span class="hljs-string">'height'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">int</span>);
}
</code></pre>
<p>Consequently, there will be no need to encapsulate multiple values within a collection or create a dedicated class for that purpose.</p>
<p>Additionally, the updated switch statement allows for the orderly deconstruction of structured patterns, while new class modifiers like <code>interface class</code> and <code>sealed class</code> offer expanded capabilities.</p>
<p>Please tell me in the comments section if you are interested in me creating a comprehensive article exploring these enhancements and demonstrating how you can leverage them to your advantage.</p>
<h3 id="heading-improvements-for-flutter-for-web">Improvements for Flutter for Web</h3>
<p>In relation to Flutter for the web, noticeable improvements have been made to enhance application loading speed. One key achievement is the substantial reduction in the size of Canvaskit, the largest component in Flutter for the web, now reduced to one-third of its previous size. Additionally, unused fonts can now be removed, further reducing the overall weight. Flutter 3.10 introduces comprehensive support for seamlessly embedding pure HTML elements within the application. Furthermore, the inclusion of fragment shader support empowers developers to create stunning visual effects using familiar Dart code.</p>
<p>In addition to all this, the Flutter team remains dedicated to ensuring seamless integration between Flutter and the new WebAssembly standard, commonly known as WASM. This involves enabling the inclusion of garbage-collected languages like Flutter within the standard. Initial tests have already demonstrated a remarkable performance boost of up to three times. With the eventual distribution of WASM to the general public, this exciting addition holds great promise for web applications developed using Flutter.</p>
<h3 id="heading-new-material-3-widgets">New Material 3 widgets</h3>
<p>This latest version of Flutter continues its efforts to enhance support for Material 3. It introduces the option to create color schemes based on either a base color or an image. Significant enhancements have been applied to various widgets, including DropdownMenu, NavigationDrawer, TabBar, SnackBar, and others, aligning them with the updated Material 3 design guidelines. Additionally, widget support for iOS/macOS has been improved. Users can now utilize Apple's spell-checking feature within editable text widgets, along with the addition of a new checkbox and radio button design that complements Cupertino aesthetics. Furthermore, there are animation improvements specific to Apple platforms.</p>
<p>Speaking of Apple, this Flutter version now enables wireless debugging directly to iPhones and iPads, a functionality previously available only in Xcode.</p>
<p>To witness all of these improvements in action, you can explore the <a target="_blank" href="https://flutter.github.io/samples/material_3.html">official sample</a> showcasing Material 3 features.</p>
<h3 id="heading-enhanced-devtools">Enhanced DevTools</h3>
<p>This version also introduces several enhancements to the development tools, empowering developers to analyze and optimize their apps' performance effectively.</p>
<p>Notably, the memory page has received new features. The addition of the Diff tool enables the comparison of memory usage before and after specific interactions, providing insights into the impact of those interactions. Furthermore, improvements have been made to streamline heap exploration directly from the console.</p>
<p>The DevTools user interface has been updated with Material 3 widgets, enhancing usability and maintaining consistency with the latest design guidelines. Additionally, the open-source tool Perfetto has been integrated, replacing the older trace viewer. Perfetto excels in managing large datasets and introduces features like pinning threads of interest, dragging and selecting multiple timeline events, and utilizing SQL queries to retrieve specific timeline data.</p>
<h3 id="heading-flutter-now-supports-slsa-level-1">Flutter now supports SLSA level 1</h3>
<p>Ensuring security is a critical aspect when working with open-source code. That's why Flutter now supports Supply Chain Levels for Software Artifacts (<a target="_blank" href="https://slsa.dev/">SLSA</a>), level 1. This implementation enables Flutter's build scripts to operate on trusted platforms. Furthermore, the release workflow for each new version of Flutter undergoes a rigorous approval process involving multiple engineers. Additionally, both beta and stable releases now come with provenance, meaning that trusted sources have constructed the final artifact that developers will utilize to build Flutter applications on their machines.</p>
<p>These measures are in response to recent security attacks and vulnerabilities observed in other open-source projects, <a target="_blank" href="https://www.bleepingcomputer.com/news/security/npm-supply-chain-attack-impacts-hundreds-of-websites-and-apps/">such as the NPM ecosystem</a>. The Flutter team is proactively taking steps to enhance the security of the ecosystem, making it more robust and reliable for developers and end users alike.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>So far, I have briefly covered the key highlights of the notable improvements in Flutter 3.10. If you wish to delve deeper into these details, I highly recommend reading the official <a target="_blank" href="https://medium.com/flutter/whats-new-in-flutter-3-10-b21db2c38c73">blog post on Flutter 3.10</a> and the blog post on <a target="_blank" href="https://medium.com/dartlang/announcing-dart-3-53f065a10635">Dart 3</a>. Additionally, I suggest watching the comprehensive collection of recently uploaded <a target="_blank" href="https://www.youtube.com/watch?v=yRlwOdCK7Ho&amp;list=PLjxrf2q8roU1523hIgnNX4r6ukX72QYLg">Flutter videos on YouTube</a>.</p>
<p>Thank you for reading thus far! Until next time!</p>
]]></content:encoded></item></channel></rss>