<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: suesitran</title>
    <description>The latest articles on DEV Community by suesitran (@suesitran).</description>
    <link>https://dev.to/suesitran</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F920039%2F8e14a115-5236-4002-b4bb-1c7d140cd45e.jpeg</url>
      <title>DEV Community: suesitran</title>
      <link>https://dev.to/suesitran</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/suesitran"/>
    <language>en</language>
    <item>
      <title>Flutter — Interactive Diary — A developer diary #13</title>
      <dc:creator>suesitran</dc:creator>
      <pubDate>Tue, 27 Sep 2022 00:04:29 +0000</pubDate>
      <link>https://dev.to/suesitran/flutter-interactive-diary-a-developer-diary-13-31kd</link>
      <guid>https://dev.to/suesitran/flutter-interactive-diary-a-developer-diary-13-31kd</guid>
      <description>&lt;h2&gt;
  
  
  Create Connectivity service
&lt;/h2&gt;

&lt;p&gt;To check device connectivity, there’s a package that works perfectly for that purpose: &lt;a href="https://pub.dev/packages/connectivity_plus"&gt;connectivity_plus&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It’s also a Flutter Favourite package, so I assume it’s safe to use right now. However, I’m being skeptical about using 3rd party directly in the app, so I’m going to wrap it inside my &lt;code&gt;ConnectivityService&lt;/code&gt; again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connectivity research
&lt;/h2&gt;

&lt;p&gt;The idea of &lt;code&gt;ConnectivityService&lt;/code&gt; is only to check if the device is having any form of connection, be it wifi, mobile, bluetooth or ethernet.&lt;/p&gt;

&lt;p&gt;For a mobile device, connecting to Wifi or Mobile can suggest that there’s internet connection on the device. However, when I was playing with my Android phone, I realised that it has Bluetooth tethering, and with little search on Google, and I came across &lt;a href="https://support.google.com/android/answer/9059108?hl=en#zippy=%2Ctether-by-bluetooth"&gt;this page&lt;/a&gt;. It means there can be internet sharing using Bluetooth connection. Therefore I added Bluetooth into my list of online status.&lt;/p&gt;

&lt;p&gt;And although it’s not common among mobile users, but connecting mobile device to ethernet to have internet access is also possible. Here is an article &lt;a href="https://au.pcmag.com/networking/86532/how-to-connect-a-phone-or-tablet-to-the-internet-using-an-ethernet-cable"&gt;how to connect your mobile to ethernet&lt;/a&gt;. So as tricky as it can be, ethernet is 1 of my online status as well.&lt;/p&gt;

&lt;p&gt;That’s all for connectivity research. Now let’s do code plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code plan
&lt;/h2&gt;

&lt;p&gt;The first need of this service is to know at any point of time, is the device having any form of connectivity? To do this, I need a boolean that returns &lt;code&gt;true/false&lt;/code&gt;, in which, &lt;code&gt;true&lt;/code&gt; means there is connectivity, and &lt;code&gt;false&lt;/code&gt; means there’s no connectivity.&lt;/p&gt;

&lt;p&gt;Next, in some cases, I’ll also need to be informed immediately when my device loses connectivity, or regains connectivity. So I’ll need a &lt;code&gt;Stream&amp;lt;bool&amp;gt;&lt;/code&gt; which will send &lt;code&gt;true/false&lt;/code&gt; value, &lt;code&gt;true&lt;/code&gt; means there is connectivity, and &lt;code&gt;false&lt;/code&gt; means there’s no connectivity.&lt;/p&gt;

&lt;p&gt;That’s simple. Let’s code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding time
&lt;/h2&gt;

&lt;p&gt;First, I’ll need to create a &lt;code&gt;boolean&lt;/code&gt; to check if device is currently having any connectivity at all. Because checking connectivity from &lt;code&gt;connectivity_plus&lt;/code&gt; will return a &lt;code&gt;Future&lt;/code&gt;, therefore my &lt;code&gt;boolean&lt;/code&gt; also needs to be a &lt;code&gt;Future&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Secondly, I need a &lt;code&gt;Stream&amp;lt;bool&amp;gt;&lt;/code&gt;, so there it is, a &lt;code&gt;Stream&amp;lt;bool&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ruoHKyC5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/km3ndf0uof97apxdkh4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ruoHKyC5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/km3ndf0uof97apxdkh4o.png" alt="First, draft out the expected result" width="720" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I need to do the actual implementation of &lt;code&gt;_checkConnectivity()&lt;/code&gt;, and &lt;code&gt;_connectivityChange&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But first, I need an instance of &lt;code&gt;Connectivity&lt;/code&gt; from &lt;code&gt;connectivity_plus&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vRzn9OCY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m89wffmeojzrdxyphv6o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vRzn9OCY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m89wffmeojzrdxyphv6o.png" alt="Define instance of Connectivity" width="720" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The optional &lt;code&gt;Connectivity?&lt;/code&gt; here is to be used later in test with a mocked instance of &lt;code&gt;Connectivity&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Then, let’s look at &lt;code&gt;_checkConnectivity()&lt;/code&gt;. The idea of this method is to return a &lt;code&gt;boolean&lt;/code&gt;, with &lt;code&gt;false&lt;/code&gt; means no connectivity, and &lt;code&gt;true&lt;/code&gt; means otherwise, so the code is as simple as this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5u6w57Vo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3t123tnv4o7rr90wxpsk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5u6w57Vo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3t123tnv4o7rr90wxpsk.png" alt="Implementation of _checkConnectivity()" width="720" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason optional &lt;code&gt;ConnectivityResult?&lt;/code&gt; is added is because this method will be reused later with &lt;code&gt;ConnectivityResult&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;Now, let’s look at &lt;code&gt;_connectivityChange&lt;/code&gt;. This is actually a &lt;code&gt;StreamController&lt;/code&gt;, which can emit a new value to the &lt;code&gt;Stream&lt;/code&gt; attached to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VpXi99gk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8giawlrptkirh8cpaokp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VpXi99gk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8giawlrptkirh8cpaokp.png" alt="StreamController to emit changes to onConnectivityChange" width="720" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are multiple connectivity types, but I only want to emit changes when connectivity is from connected to none. I do not want to be informed when connectivity is switching from 1 type to another. Therefore I need a boolean to know the current connectivity status, and will emit change if and only if connectivity is change from connected to none.&lt;/p&gt;

&lt;p&gt;To do this, I’ll create a &lt;code&gt;boolean&lt;/code&gt; for current connectivity, and check connectivity as soon as the instance is created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1FG0q0XB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/34pplorf17y3xu3ty9ou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1FG0q0XB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/34pplorf17y3xu3ty9ou.png" alt="Create boolean to keep track of current connection status" width="720" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it’s time to work on the connectivity change. &lt;code&gt;Connectivity&lt;/code&gt; class has a &lt;code&gt;Stream&lt;/code&gt; that emit &lt;code&gt;ConnectivityStatus&lt;/code&gt;. I will just need to listen to this stream, and check when connection change from connected to none, and emit the change when connection status is changed. I’ll do this in the constructor as well, so the constructor becomes this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K4SrBJx8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wudufx7mgivfjhb7pa1d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K4SrBJx8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wudufx7mgivfjhb7pa1d.png" alt="Update constructor to listen to changes in onConnectivityChange" width="720" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And last step, add implementation of &lt;code&gt;_handleConnectivityChange()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_-YlEK-D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qhqzzzodmk25gdt0f4fw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_-YlEK-D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qhqzzzodmk25gdt0f4fw.png" alt="Implementation of  raw `_handleConnectivityChange()` endraw " width="720" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it. Now, test time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test time
&lt;/h2&gt;

&lt;p&gt;To test my &lt;code&gt;ConnectivityService&lt;/code&gt;, I’ll need to mock the behaviour of &lt;code&gt;Connectivity&lt;/code&gt; class. To do this, I’ll need to use &lt;code&gt;Mockito&lt;/code&gt; package, as well as &lt;code&gt;build_runner&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WJZrOF-z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xuaorxrab37kq39yd2ww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WJZrOF-z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xuaorxrab37kq39yd2ww.png" alt="Add mockito and build_runner packages" width="600" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These packages are added under &lt;code&gt;dev_dependencies&lt;/code&gt; because I only need them during development.&lt;/p&gt;

&lt;p&gt;The test class name is &lt;code&gt;nartus_connectivity_test.dart&lt;/code&gt;, which is the same file name as the class I want to test, and add suffix &lt;code&gt;_test&lt;/code&gt; to it. This is to make it easier to find which class this test is about, and also easier for me to write script later if I need to.&lt;/p&gt;

&lt;p&gt;To generate the mocks to classes I need, I add this annotation right above the main method&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m11w9eGO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qcczyd1sun14ujfnpt1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m11w9eGO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qcczyd1sun14ujfnpt1y.png" alt="Annotation to generate Mock" width="622" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that, I need to run following command to generate the mock file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YwnTD0ZE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xb63z8dfdlhjdbd94ccv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YwnTD0ZE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xb63z8dfdlhjdbd94ccv.png" alt="Run build runner command to generate mock file" width="694" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This command is included in &lt;code&gt;scripts/generate_dependencies.sh&lt;/code&gt;, so my actual command is&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uJjNs1Gz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v1syeaph23wmp0a9d5tc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uJjNs1Gz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v1syeaph23wmp0a9d5tc.png" alt="Run script to generate mock files" width="712" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are pro and con in doing it this way. Pro is that it saves time for me to remember the command to run, but con is that it takes longer time, because this script will run in all packages. As I add more packages to the project, this script will take longer time to run. Maybe I need to find a better way to do this.&lt;/p&gt;

&lt;p&gt;Anyway, back to the test. Now I have the mock file generated, I can import it into my test, and start making an instance of the mock, and start testing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0EjfaYln--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/at1wubywdrmr7ghlkoyw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0EjfaYln--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/at1wubywdrmr7ghlkoyw.png" alt="Create instance of MockConnectivity" width="720" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In each of the test, it’s always a good practice to give the condition of the test (GIVEN), then action to do (WHEN), and the expected result (THEN). This way, it’s clear what the test is about.&lt;/p&gt;

&lt;p&gt;In the first test, I want to ensure that isConnected will return false if current connectivity is none, so I give the description of the test as&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;given current connectivity is none, when get isConnected, then return false&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lHePy6df--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8pkufdlqvgaza4dp4dzh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lHePy6df--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8pkufdlqvgaza4dp4dzh.png" alt="isConnected returns false when connectivity is none" width="720" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, I need to test with other connectivity types as well: bluetooth, wifi, mobile, ethernet.&lt;/p&gt;

&lt;p&gt;Next, I’ll need to test the connectivity stream. I know that when connectivity is changed from none to other connectivity type, I expect there is a value emit to my stream. So the description of this test will be&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;given current connectivity is none, when connectivity change to mobile, then emit true to connectivity change stream&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mlhC1m3q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vz0zn32lyohczhhcnosa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mlhC1m3q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vz0zn32lyohczhhcnosa.png" alt="Test when connectivity changes from none to mobile" width="720" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I continue to test the same scenario with other connectivity types: wifi, bluetooth, ethernet.&lt;/p&gt;

&lt;p&gt;Another case to test is when changing between these connectivity type, there should be no value emit to my stream. So the description of the test will be&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;given current connectivity is mobile, when connectivity change to bluetooth, then do not emit changes to stream&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hSl320JM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipmqu4u6maaev8qt4r3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hSl320JM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipmqu4u6maaev8qt4r3b.png" alt="Test to ensure there’s no value emit to stream when connectivity switch from 1 to another" width="720" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, I need to test that when there are multiple connectivity change happens, but &lt;code&gt;onConnectivityChange&lt;/code&gt; will only emit 1 change when connectivity is changed to none.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;given connectivity switches multiple times, when listening on stream, then only emits value when Connectivity change to none&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--onMkaB_6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxs4k48ssnzpsnujl03e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--onMkaB_6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxs4k48ssnzpsnujl03e.png" alt="Test to ensure that there is only 1 value emit when connectivity changes from connected to none" width="720" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s it, my &lt;code&gt;ConnectivityService&lt;/code&gt; is done.&lt;/p&gt;




&lt;p&gt;That’s all for today. If you like my article, please follow for more tips.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>interactivediary</category>
      <category>programming</category>
    </item>
    <item>
      <title>Flutter — Interactive Diary — A developer diary #12</title>
      <dc:creator>suesitran</dc:creator>
      <pubDate>Sun, 18 Sep 2022 11:17:32 +0000</pubDate>
      <link>https://dev.to/suesitran/flutter-interactive-diary-a-developer-diary-12-2fcd</link>
      <guid>https://dev.to/suesitran/flutter-interactive-diary-a-developer-diary-12-2fcd</guid>
      <description>&lt;h2&gt;
  
  
  Let’s BLoC
&lt;/h2&gt;

&lt;p&gt;The work I pickup today is Location Permission handling, which is divided into 2 parts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/suesitran/interactive_diary/issues/26"&gt;Part 1:&lt;/a&gt; Handle location permission within app&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/suesitran/interactive_diary/issues/61"&gt;Part 2:&lt;/a&gt; Handle location permission between app and App Settings&lt;/p&gt;

&lt;p&gt;Actually the work for Part 1 has been done &lt;a href="https://github.com/suesitran/interactive_diary/pull/37"&gt;by KoC&lt;/a&gt;, however, we did not have a good technical discussion before the work started, therefore he did not use the correct lib to open the native App Setting page, and so the work is undone. That’s why I continue his work any complete both Part 1 and Part 2 of this issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyse issue
&lt;/h2&gt;

&lt;p&gt;Before I start coding, I need to understand all scenarios in the issue (story) that’s assigned to me. And to do that, I start by translating scenarios from user’s point of view into technical point of view.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Convert scenarios to technical knowledge&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is the link to the issue I need to implement: &lt;a href="https://github.com/suesitran/interactive_diary/issues/26"&gt;Location Permission&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each scenario describes a small technical requirement I will need to implement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scenario 1:&lt;br&gt;
GIVEN Location permission is denied&lt;br&gt;
AND Internet connection is available&lt;br&gt;
AND I do not have any location stored in storage&lt;br&gt;
WHEN I launch the app&lt;br&gt;
THEN I’ll be presented a dialog to explain why location permission is needed&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This part means “when location permission is NOT granted”, then show dialog to explain why location permission is needed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scenario 2:&lt;br&gt;
GIVEN scenario 1&lt;br&gt;
WHEN I tap on ‘Continue’ button&lt;br&gt;
THEN the map is loaded with default location lat/lng&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the dialog above, the user taps on &lt;code&gt;Continue&lt;/code&gt; button, then dismiss the dialog, and show the map with default location.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scenario 3:&lt;br&gt;
GIVEN scenario 1&lt;br&gt;
WHEN I tap on ‘Allow’ button&lt;br&gt;
THEN native Location permission request dialog is presented to me&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the dialog in scenario 1, when user taps on &lt;code&gt;Allow&lt;/code&gt; button, then dismiss the dialog, and request native location permission&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scenario 4:&lt;br&gt;
GIVEN scenario 3&lt;br&gt;
WHEN I tap on ‘Deny’ button&lt;br&gt;
THEN Location permission dialog is dismissed&lt;br&gt;
AND I’ll be presented a dialog to explain why location permission is needed&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When native location permission dialog is showing, if user denies permission, then we show dialog in scenario 1 again. This is when Location permission status is &lt;code&gt;NOT GRANTED&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scenario 5:&lt;br&gt;
GIVEN scenario 3&lt;br&gt;
WHEN I tap on ‘Deny, do not ask again’&lt;br&gt;
THEN Location permission dialog is dismissed&lt;br&gt;
THEN I’ll be presented a dialog to explain why location permission is needed&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Android, there’s a term for permission permanently denied, and similarly, in iOS, there’s also a similar term. So this scenario describes this case, when user permanently denied location permission. When this happens, if I try to request native location permission dialog, nothing happens, and location permission status remains &lt;code&gt;FOREVER NOT GRANTED&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scenario 6:&lt;br&gt;
GIVEN scenario 5&lt;br&gt;
WHEN I tap on ‘Allow’ button&lt;br&gt;
THEN I’ll be navigated to native App Setting screen, where I can change location permission for my app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This continues from scenario 5 above, in the dialog within my app, when user taps &lt;code&gt;Allow&lt;/code&gt;, because the permission now is &lt;code&gt;FOREVER NOT GRANTED&lt;/code&gt;, and requesting native location dialog will take no effect, the only way for user to allow location permission to this app is to change it in App Setting screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary of scenarios&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All 6 scenarios above can be summarised to this short technical requirement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When request current user’s location, and location permission is granted, then show Map with current user’s location&lt;br&gt;
When request current user’s location, if location permission is NOT GRANTED, then show Dialog 1 to explain location needs&lt;br&gt;
If user taps Continue in Dialog 1, then load default location&lt;br&gt;
If user taps Allow in Dialog 1, then&lt;br&gt;
=&amp;gt; If location permission is NOT GRANTED, then request native location permission dialog&lt;br&gt;
=&amp;gt; If location permission is FOREVER NOT GRANTED, then open App Settings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s easier to understand now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech time
&lt;/h2&gt;

&lt;p&gt;After understanding the requirement in tech’s POV, it’s time to do code plan. This time, since most of the logic is done by KoC, I just need to continue base on that logic.&lt;/p&gt;

&lt;p&gt;I’ll be using &lt;code&gt;flutter_bloc&lt;/code&gt; for this complex issue. There’s a choice between using &lt;code&gt;bloc&lt;/code&gt;, or just &lt;code&gt;cubit&lt;/code&gt;, but for learning purpose, I choose to use bloc this time. Maybe next time I’ll use cubit.&lt;/p&gt;

&lt;p&gt;As it’s quite complex issue, it’s better to draw a diagram of the flow.&lt;/p&gt;

&lt;p&gt;First diagram is how each component send request to its processor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ujr6ONkT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tz5gw5aw7ee3xczrk8ft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ujr6ONkT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tz5gw5aw7ee3xczrk8ft.png" alt="Request flow" width="700" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And how the processor returns result to the requester.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0naSyqVl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ac84wfrsf54ctnam6qmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0naSyqVl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ac84wfrsf54ctnam6qmz.png" alt="Results are return to requester" width="700" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the &lt;code&gt;UI&lt;/code&gt; will only communicate to the &lt;code&gt;BLoC&lt;/code&gt;, the &lt;code&gt;BLoC&lt;/code&gt; will communicate to &lt;code&gt;Location Wrapper&lt;/code&gt;, and &lt;code&gt;Location Wrapper&lt;/code&gt; will communicate with &lt;code&gt;Location&lt;/code&gt; and &lt;code&gt;Permission Handler&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are 3 notes here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The reason for breakdown communication is to simplify the flow between each component, and makes it easier to test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Location Wrapper&lt;/code&gt; is in-place in order to make the code modular and easy to change or update later if there’s ever a need to change &lt;code&gt;Location&lt;/code&gt; package or &lt;code&gt;Permission Handler&lt;/code&gt; package, and also ensure to reduce changes to the main app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using &lt;code&gt;Permission Handler&lt;/code&gt; to handle location permission in stead of using &lt;code&gt;Location&lt;/code&gt;’s permission handler is because &lt;code&gt;Permission Handler&lt;/code&gt; also provide convenient method to open &lt;code&gt;App Settings&lt;/code&gt;. Besides, it also provides handling to other permissions that I’ll need later.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now I need to plan activity between each components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code plan
&lt;/h2&gt;

&lt;p&gt;Let’s do code plan for each block.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For UI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What’s needed in the UI?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UI&lt;/code&gt; will need to show &lt;code&gt;Dialog 1&lt;/code&gt;, with a &lt;code&gt;Text&lt;/code&gt; for title, a &lt;code&gt;Text&lt;/code&gt; for content, and 2 &lt;code&gt;TextButton&lt;/code&gt;s for buttons.&lt;/p&gt;

&lt;p&gt;When is &lt;code&gt;Dialog 1&lt;/code&gt; needed? When &lt;code&gt;Location Permission&lt;/code&gt; is &lt;code&gt;NOT GRANTED&lt;/code&gt;, aka, &lt;code&gt;DENIED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;How does &lt;code&gt;UI&lt;/code&gt; know location permission is &lt;code&gt;DENIED&lt;/code&gt;? use &lt;code&gt;LocationPermissionDeniedState&lt;/code&gt; from &lt;code&gt;Location BLoC&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When user taps on &lt;code&gt;Allow&lt;/code&gt; button in &lt;code&gt;Dialog 1&lt;/code&gt;, send event to &lt;code&gt;LocationBLoC&lt;/code&gt; to request location permission.&lt;/p&gt;

&lt;p&gt;But if &lt;code&gt;Location&lt;/code&gt; permission is &lt;code&gt;PERMANENTLY NOT GRANTED&lt;/code&gt;, aka &lt;code&gt;PERMANENTLY DENIED&lt;/code&gt;, then the wording on &lt;code&gt;Allow&lt;/code&gt; button needs to be different, so that user knows he’s being taken to &lt;code&gt;App Settings&lt;/code&gt; screen right?&lt;/p&gt;

&lt;p&gt;OK, then let’s have a state for &lt;code&gt;PERMANENTLY DENIED&lt;/code&gt;, call it &lt;code&gt;LocationPermissionDeniedForeverState&lt;/code&gt;, then &lt;code&gt;Dialog 1&lt;/code&gt; will show with &lt;code&gt;TextButton&lt;/code&gt; as &lt;code&gt;Open Settings&lt;/code&gt;, and &lt;code&gt;Continue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Location BLoC&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From &lt;code&gt;UI&lt;/code&gt; plan above, I know there are 2 States I’ll need in Location BLoC: &lt;code&gt;LocationPermissionDeniedState&lt;/code&gt; and &lt;code&gt;LocationPermissionDeniedForeverState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;How to reach &lt;code&gt;LocationPermissionDeniedState&lt;/code&gt;? When permission is &lt;code&gt;DENIED&lt;/code&gt;, and &lt;code&gt;NOT PERMANENTLY DENIED&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;How to reach &lt;code&gt;LocationPermissionDeniedForeverState&lt;/code&gt;? When permission is &lt;code&gt;PERMANENTLY DENIED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These info are from &lt;code&gt;Location Wrapper&lt;/code&gt;, in term of &lt;code&gt;Exceptions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LocationPermissionDeniedException&lt;/code&gt; =&amp;gt; State is &lt;code&gt;LocationPermissionDeniedState&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LocationPermissionDeniedForeverException&lt;/code&gt; =&amp;gt; State is &lt;code&gt;LocationPermissionDeniedForeverState&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For the events, there will be 2 events:&lt;/p&gt;

&lt;p&gt;Request to show native location permission event =&amp;gt; call location wrapper to show native location permission request dialog&lt;/p&gt;

&lt;p&gt;Request to open App Settings =&amp;gt; call location wrapper to open app settings&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Location Wrapper&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Location Wrapper&lt;/code&gt; class will wrap around &lt;code&gt;location&lt;/code&gt; and &lt;code&gt;permission_handler&lt;/code&gt; packages, and use these packages for each needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;location&lt;/code&gt; package: to get user’s current lat/lng value&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;permission_handler&lt;/code&gt; package: to handle permission status, request native location permission request dialog, and open app settings.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within this issue today, I’ll focus on location permission, which is mainly maintained by &lt;code&gt;permission_handler&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;As code plan above, this &lt;code&gt;Location Wrapper&lt;/code&gt; class will need to throw 2 Exceptions&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;LocationPermissionDeniedException&lt;/code&gt;: When permission status is denied, limited, or restricted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;LocationPermissionDeniedForeverException&lt;/code&gt;: when permission status is permanently_denied.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it. Simple. Now it’s time to code.&lt;/p&gt;

&lt;p&gt;Code time&lt;br&gt;
First step, I need to add permission_handler into pubspec.yaml&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UWpki7sk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mq0zuf60okvj8ro6yo7h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UWpki7sk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mq0zuf60okvj8ro6yo7h.png" alt="Add permission_handler to pubspec.yaml" width="604" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I need to run &lt;code&gt;flutter pub get&lt;/code&gt; to update my pub. Actually let’s leave it to Android Studio, and I just click the Pub get button instead.&lt;/p&gt;

&lt;p&gt;Second step, edit &lt;code&gt;LocationService&lt;/code&gt; class, inside method &lt;code&gt;getCurrentLocation()&lt;/code&gt;, and convert permission status into &lt;code&gt;Exceptions&lt;/code&gt; as needed&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r7rtolGY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/16jsh6gfos2q2kfl6kmu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r7rtolGY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/16jsh6gfos2q2kfl6kmu.png" alt="Convert permission status into Exception" width="700" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also need to add methods to request permission, and open app settings&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7WR7u7Wf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lggwxrufq9oot7k5qmdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7WR7u7Wf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lggwxrufq9oot7k5qmdv.png" alt="Request permission method" width="700" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;requestPermission&lt;/code&gt; method, I also wanna know what option user chooses, by checking permission again after user makes their choice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dZDNirRN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/790w6i0rbf1dq6ohbzdc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dZDNirRN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/790w6i0rbf1dq6ohbzdc.png" alt="open App Settings" width="700" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;requestOpenAppSettings&lt;/code&gt; is straight forward, no tricky logic here.&lt;/p&gt;

&lt;p&gt;Next step, I need to update &lt;code&gt;LocationBloc&lt;/code&gt;, to add 2 new states.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w-LaKxBL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7eyjgn5xzoc6omt7vt5i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w-LaKxBL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7eyjgn5xzoc6omt7vt5i.png" alt="New states in LocationBloc" width="700" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And I also need 2 new events for &lt;code&gt;LocationBloc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7BrzLWw_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4qr2l072dsrv9rnbgsz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7BrzLWw_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4qr2l072dsrv9rnbgsz.png" alt="New events for LocationBloc" width="700" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, implement handler for those events&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pA0zS2KQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksigx9euyeh6cyl6r3zq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pA0zS2KQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksigx9euyeh6cyl6r3zq.png" alt="Handle events from UI" width="700" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And implement functions for each event&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rfkxtwEy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pootilwgqi5rjz8xpm7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rfkxtwEy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pootilwgqi5rjz8xpm7c.png" alt="Functions to handle events" width="700" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that in &lt;code&gt;_showDialogRequestPermissionEvent()&lt;/code&gt;, when use allow location permission, I immediately request current location. This is to refresh UI immediately with updated user’s currently location.&lt;/p&gt;

&lt;p&gt;That’s almost it. The next step is to update the code in UI, but the code is too long to copy here, so here is the &lt;a href="https://github.com/suesitran/interactive_diary/pull/62"&gt;link to my PR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, there’s something I did not anticipate during code plan, which is when user returns from App Settings. There are 2 scenarios when user returns from App Settings, and I described them in &lt;a href="https://github.com/suesitran/interactive_diary/issues/61"&gt;this issue here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In order to accommodate this new scenarios, I changed my &lt;code&gt;StatelessWidget&lt;/code&gt; to be a &lt;code&gt;StatefulWidget&lt;/code&gt;, in order to use &lt;code&gt;WidgetsBindingObserver&lt;/code&gt; to listen to &lt;code&gt;AppLifecycleState&lt;/code&gt; changes.&lt;/p&gt;

&lt;p&gt;And I added new state to my LocationBloc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MFtcw10p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zob8cb6lrh68g75wpdnl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MFtcw10p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zob8cb6lrh68g75wpdnl.png" alt="New State when user is navigating to App Settings screen" width="700" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And new event as well&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dVYUIqJe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/14eqeybm3ijjqscr6brt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dVYUIqJe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/14eqeybm3ijjqscr6brt.png" alt="New event when user return from App Setting screen" width="700" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When user taps on Open Settings button, I change the state to &lt;code&gt;AwaitLocationPermissionFromAppSettingsState&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1bX3YD6S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ukeohwpxmi7fgvgwdhz0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1bX3YD6S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ukeohwpxmi7fgvgwdhz0.png" alt="Emit new state when open App Settings" width="700" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And by using &lt;code&gt;WidgetsBindingObserver&lt;/code&gt;, I can now override &lt;code&gt;didChangeAppLifecycleState&lt;/code&gt; in the UI, and notify &lt;code&gt;LocationBloc&lt;/code&gt; when users returns to the app&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qjUkMM28--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vwzr7rj5kevofhw2wal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qjUkMM28--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vwzr7rj5kevofhw2wal.png" alt="Handle app resume state" width="700" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then when &lt;code&gt;LocationBloc&lt;/code&gt; received event &lt;code&gt;ReturnedFromAppSettingsEvent&lt;/code&gt;, it will just try to get user’s current location again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D6d21w0U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nr5wx5jhvhqj1t493tor.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D6d21w0U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nr5wx5jhvhqj1t493tor.png" alt="Request current location again when user returns from App Settings" width="700" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it. The rest is tinkering work to make it works, and write test.&lt;/p&gt;

&lt;p&gt;Full code can be found here in &lt;a href="https://github.com/suesitran/interactive_diary/pull/62"&gt;this PR&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;That’s all for today. If you like my article, please follow for more tips.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Flutter — Interactive Diary — A developer diary #11</title>
      <dc:creator>suesitran</dc:creator>
      <pubDate>Sun, 11 Sep 2022 10:53:33 +0000</pubDate>
      <link>https://dev.to/suesitran/flutter-interactive-diary-a-developer-diary-11-3636</link>
      <guid>https://dev.to/suesitran/flutter-interactive-diary-a-developer-diary-11-3636</guid>
      <description>&lt;h2&gt;
  
  
  Have fun with Animations
&lt;/h2&gt;

&lt;p&gt;Let’s put the Dev hat on, and enjoy the fun of code today.&lt;/p&gt;

&lt;p&gt;As I’m switching back to Dev role, I look for the most interesting work I always like: Animation. And there’s 1 story fits nicely in my interest: &lt;a href="https://github.com/suesitran/interactive_diary/issues/33"&gt;Core UI — Date text field with error integration.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the design I came up with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EZnr6oGa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1t3bvx9bpq4ddzoxzdps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EZnr6oGa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1t3bvx9bpq4ddzoxzdps.png" alt="Text field without error&amp;lt;br&amp;gt;
" width="361" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And it can be transform into below UI, when error is shown&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QmIGfWFt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nppa7yn1pu6p9u5rvapn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QmIGfWFt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nppa7yn1pu6p9u5rvapn.png" alt="Text field with error" width="362" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also came up with a nice description of the animation I wish to have&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Animation&lt;br&gt;
When switching from normal state to error state, the error banner should slide down from behind the Date banner&lt;br&gt;
When switching from error state to banner state, the error banner should slide up to hide behind the Date banner&lt;br&gt;
Animation duration is 300ms&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, let’s start coding.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code Plan
&lt;/h2&gt;

&lt;p&gt;Code plan is actually to analyse the problem at hand, and try to solve it with a plan, before I write any code at all.&lt;/p&gt;

&lt;p&gt;For simple problem, I usually just think of what to do, then remember the step, and start writing the code. But for this problem, there are actually 2 different hard-to-remember solutions I can try, so I’m gonna write them down here, in case I may forget them.&lt;/p&gt;

&lt;p&gt;The first step is to create the UI. Looking at the 2 UI (with and without error), There are 2 ways to create this UI&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;Stack&lt;/code&gt;, with Label on top, and Error underneath&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;Column&lt;/code&gt;, and show/hide error as needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let’s analyse each solution&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution 1 — Stack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I’ll need a &lt;code&gt;Stack&lt;/code&gt;, a large text for Label, and smaller text for error&lt;/p&gt;

&lt;p&gt;Stack: need error background colour, so that when the error text is showed, this background colour will be showed.&lt;/p&gt;

&lt;p&gt;Label: need container to wrap around it because I need the decoration, with border radius, and background colour&lt;/p&gt;

&lt;p&gt;Error: Just text, white colour, with some padding&lt;br&gt;
Now, let’s think of animation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When switching from normal state to error state, the error banner should slide down from behind the Date banner.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This suggest I can use &lt;code&gt;SlideTransition&lt;/code&gt;. But if I use &lt;code&gt;SlideTransition&lt;/code&gt; on the error text, only the error text will slide down, without the red background colour. So no, not &lt;code&gt;SlideTransition&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next implementation I can think of is to create Error with its own container, with decoration and background colour to be error colour, then use &lt;code&gt;AnimatedBuilder&lt;/code&gt; to update the height of this container.&lt;/p&gt;

&lt;p&gt;But to what height should I change this container’s height to?&lt;/p&gt;

&lt;p&gt;I know the size of the Error text, I know the padding value I wanna give to it. So maybe, I can calculate this height.&lt;/p&gt;

&lt;p&gt;Okie, this sounds fun, although it does not sound like the best solution. Why? Because&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;with Accessibility large text, there’s a good chance that the calculation will fail&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and each time to show/hide error, I’ll have to do a calculation again.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds silly, but actually it will work. Actually I coded this as well in &lt;a href="https://github.com/suesitran/interactive_diary/pull/40"&gt;this PR&lt;/a&gt;, and tried to stop here, because this works.&lt;/p&gt;

&lt;p&gt;But after thinking again and again, this is not best approach yet. So, let’s look at the 2nd solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution 2 — Column&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This solution is straight-forward. First, I create a &lt;code&gt;Column&lt;/code&gt; with 2 children: the &lt;code&gt;Text&lt;/code&gt; for Label, and the &lt;code&gt;Text&lt;/code&gt; for error.&lt;/p&gt;

&lt;p&gt;I’ll need some decoration for Label, border radius, background colour, text size, text colour.&lt;/p&gt;

&lt;p&gt;Then I’ll look at the error. If I create decoration for the Error, when the error is showed, it will totally be separated from the Label, and I will not have the effect that the Error is extended from the Label above.&lt;/p&gt;

&lt;p&gt;To solve this, I need a container to wrap outside of the &lt;code&gt;Column&lt;/code&gt;, and apply decoration for the Error on this &lt;code&gt;Container&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When there’s no Error, this decoration is totally hidden behind the Label decoration, but when there is error, the lower part will be visible as if it’s the background of the error.&lt;/p&gt;

&lt;p&gt;But wait, there’s shadow in the UI. In this case, I can wrap them in a &lt;code&gt;Card&lt;/code&gt;, and use its elevation value to show shadow, and I can also decorate the &lt;code&gt;Card&lt;/code&gt; by updating its shape.&lt;/p&gt;

&lt;p&gt;Now, the animation part. The simplest animation is to change the height of this Error text, and create the illusion that the text slide down from underneath the Label.&lt;/p&gt;

&lt;p&gt;The only thing I’m worried here is that the text in Error may be distorted during animation.&lt;/p&gt;

&lt;p&gt;If I use the SizeTransition widget, I can change the value of axisAlignment, so maybe this can help saving the text from being distorted due to layout change.&lt;/p&gt;

&lt;p&gt;That sounds like a good plan. It’s now time to code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Write the code
&lt;/h2&gt;

&lt;p&gt;Here is the &lt;a href="https://github.com/suesitran/interactive_diary/pull/43"&gt;link to the complete code of solution 2&lt;/a&gt;. And below is detail how the Code plan matches the code.&lt;/p&gt;

&lt;p&gt;First step, create a &lt;code&gt;StatelessWidget&lt;/code&gt; to host the custom widget I want to create.&lt;/p&gt;

&lt;p&gt;Then, in the build method of this &lt;code&gt;StatelessWidget&lt;/code&gt;, I create a &lt;code&gt;Column&lt;/code&gt; widget, with 2 children: 1 &lt;code&gt;Text&lt;/code&gt; for label, and 1 &lt;code&gt;Text&lt;/code&gt; for error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Column(
  children: [
    Text('label'),
    Text('error'),
  ],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I need decoration for the label text, so I’ll separate it out to another method, to make it easy to implement and edit later. And I’ll do the same for error text as well, because it’ll need animation. The code now becomes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Column(
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    _labelWidget(),
    _errorWidget(),
  ],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this time, the 2 methods &lt;code&gt;_labelWidget()&lt;/code&gt; and &lt;code&gt;_errorWidget()&lt;/code&gt; are simply returning Text widget.&lt;/p&gt;

&lt;p&gt;Now, let’s work on this line&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ll need some decoration for Label, border radius, background colour, text size, text colour.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This implementation will go into &lt;code&gt;_labelWidget()&lt;/code&gt; method, and I’ll need a &lt;code&gt;Container&lt;/code&gt; widget for this. And also need to update the &lt;code&gt;textStyle&lt;/code&gt; of this label to match the design.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Widget _labelWidget() =&amp;gt; Container(
  alignment: Alignment.center,
  decoration: BoxDecoration(
      color: Theme.of(context).colorScheme.onBackground,
      borderRadius: const BorderRadius.all(
          Radius.circular(NartusDimens.padding24))),
  padding: const EdgeInsets.symmetric(
      horizontal: NartusDimens.padding16,
      vertical: NartusDimens.padding14),
  child:
  Text('Label', style: Theme.of(context).textTheme.headline6),
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I also need to update the padding for my error text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Widget _errorWidget() =&amp;gt; Padding(
  padding: const EdgeInsets.symmetric(
      horizontal: NartusDimens.padding16,
      vertical: NartusDimens.padding4),
  child: Text(
    'error',
    style: Theme.of(context)
        .textTheme
        .subtitle1
        ?.copyWith(color: Theme.of(context).colorScheme.onError),
    textAlign: TextAlign.center,
  ),
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right now, the UI of this widget component looks like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p1SOrERb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pqz82tzik5r9a4d8n5mf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p1SOrERb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pqz82tzik5r9a4d8n5mf.png" alt="First draft of text_and_error_label widget" width="401" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I still need decoration for the background of error text. So I’ll wrap the &lt;code&gt;Column&lt;/code&gt; inside a &lt;code&gt;Card&lt;/code&gt; widget, then do decoration on the &lt;code&gt;Card&lt;/code&gt; itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Card(
  shape: const RoundedRectangleBorder(
    borderRadius: BorderRadius.all(Radius.circular(NartusDimens.padding24)),
  ),
  color: Theme.of(context).colorScheme.error,
  elevation: NartusDimens.padding4,
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.center,
    children: [
      _labelWidget(),
      _errorWidget(),
    ],
  ),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Fvvv5pQv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6t16avvcid5i4o7y88g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Fvvv5pQv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6t16avvcid5i4o7y88g.png" alt="text_and_error_label after Card decoration" width="397" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it looks matching the designed UI. It’s time to implement animation.&lt;/p&gt;

&lt;p&gt;In order to add animation to this widget, I’ll need to use &lt;code&gt;SingleTickerProviderStateMixin&lt;/code&gt;, and with this mixin, I’ll need to convert my &lt;code&gt;StatelessWidget&lt;/code&gt; to be a &lt;code&gt;StatefulWidget&lt;/code&gt;, then implement the &lt;code&gt;State&lt;/code&gt; of my &lt;code&gt;StatefulWidget&lt;/code&gt; with &lt;code&gt;SingleTickerProviderStateMixin&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class _TextAndErrorLabelState extends State&amp;lt;TextAndErrorLabel&amp;gt;
 with SingleTickerProviderStateMixin {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actually I used Android Studio to do the conversion, so it’s like there’s no work on me at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TnMiaqG1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p3uu9hsyjoiivi0kpcb0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TnMiaqG1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p3uu9hsyjoiivi0kpcb0.png" alt="Android Studio can easily convert StatelessWidget to StatefulWidget" width="471" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m going to use &lt;code&gt;SizeTransition&lt;/code&gt; widget, so I’ll need an &lt;code&gt;AnimationController&lt;/code&gt; to control my animation, which will have the duration of 300ms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;late final AnimationController _controller = AnimationController(
    vsync: this, duration: const Duration(milliseconds: 300));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and an Animation with double for the actual animation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;late final Animation&amp;lt;double&amp;gt; _sizeAnimation =
    CurvedAnimation(curve: Curves.fastOutSlowIn, parent: _controller);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller will need a value, so I’ll define this value in &lt;code&gt;initState()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@override
void initState() {
  super.initState();

  _controller.value = 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And of course, I also need to dispose the controller when I no longer need it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@override
void dispose() {
  super.dispose();

  _controller.dispose();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All setup is done, now it's the time to wrap the error inside a &lt;code&gt;SizeTransition&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SizeTransition&lt;/code&gt; widget will take full space of parent’s width, so my error text will no longer be center align. Therefore I need to change to use &lt;code&gt;Container&lt;/code&gt; to wrap it, in order to have both padding and alignment in 1 container.&lt;/p&gt;

&lt;p&gt;I also change the &lt;code&gt;axisAlignment&lt;/code&gt; value to 1 (default is 0) because I want the &lt;code&gt;Container&lt;/code&gt; to update the size from the bottom of its height, not the center or top.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Widget _errorWidget() =&amp;gt; SizeTransition(
      sizeFactor: _sizeAnimation,
      axisAlignment: 1,
      child: Container(
        alignment: Alignment.bottomCenter,
        padding: const EdgeInsets.symmetric(
            horizontal: NartusDimens.padding16,
            vertical: NartusDimens.padding4),
        child: Text(
          'error',
          style: Theme.of(context)
              .textTheme
              .subtitle1
              ?.copyWith(color: Theme.of(context).colorScheme.onError),
          textAlign: TextAlign.center,
        ),
      ),
    );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I think this is done.&lt;/p&gt;

&lt;p&gt;But wait. Is it done? There’s no animation although I already added &lt;code&gt;SizeTransition&lt;/code&gt; in my error text.&lt;/p&gt;

&lt;p&gt;The reason is because I still need to use the &lt;code&gt;AnimationController&lt;/code&gt; to start my animation. To do this, simply run &lt;code&gt;_controller.forward();&lt;/code&gt; when it’s needed. In my case, because I’m going to use this widget in a bloc, so I’ll need it to run animation immediately as it’s showed, therefore this code will be in my &lt;code&gt;build()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;And that’s it. The animation works!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--msqZJ3cv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xtgy6rzde8xqru5duimu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--msqZJ3cv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xtgy6rzde8xqru5duimu.png" alt="Text and Error label demo with animation" width="406" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full source code for this widget with animation can be found &lt;a href="https://github.com/suesitran/interactive_diary/blob/dev/packages/nartus_ui_package/lib/widgets/text_error_label/text_and_error_label.dart"&gt;in this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That’s all for today. If you like my post, please follow me for more tips.&lt;/p&gt;

</description>
      <category>flutter</category>
    </item>
  </channel>
</rss>
