<?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>Forem: ente</title>
    <description>The latest articles on Forem by ente (@enteio).</description>
    <link>https://forem.com/enteio</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%2F865736%2Ffb5ee061-1fe6-4f4d-ab0b-11961b7ec451.jpeg</url>
      <title>Forem: ente</title>
      <link>https://forem.com/enteio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/enteio"/>
    <language>en</language>
    <item>
      <title>Understanding Keys, Elements, RenderObjects and their interplay in Flutter</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Thu, 28 Sep 2023 08:22:37 +0000</pubDate>
      <link>https://forem.com/enteio/understanding-keys-elements-renderobjects-and-their-interplay-in-flutter-1o0j</link>
      <guid>https://forem.com/enteio/understanding-keys-elements-renderobjects-and-their-interplay-in-flutter-1o0j</guid>
      <description>&lt;p&gt;Understanding the inner workings of Flutter is crucial to crafting bug-free and performant apps. While working on performance optimisation of &lt;a href="https://ente.io/"&gt;ente&lt;/a&gt;'s mobile app, I encountered some intriguing behaviour, prompting me to write this blog. This blog isn’t focused on how to make performant apps but understanding the concepts written here will indirectly help with it. In this blog post, I’ll primarily delve into,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How Elements and RenderObjects are reused in Flutter.&lt;/li&gt;
&lt;li&gt;An overview of the three trees in Flutter. &lt;/li&gt;
&lt;li&gt;An example that illustrates a state management issue which can be easily debugged from the insights of the first two.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Links to resources that might help you understand this blog better if you find it difficult is at the end.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Example
&lt;/h3&gt;

&lt;p&gt;Let’s create a StatefulWidget for a container which accepts a parameter &lt;code&gt;name&lt;/code&gt; and has a colour which is randomly generated. The colour will be stored in its state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomContainer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_CustomContainerState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_CustomContainerState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getRandomColor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Color&lt;/span&gt; &lt;span class="nf"&gt;getRandomColor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromRGBO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nextInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nextInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nextInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create another StatefulWidget which has a list of three &lt;code&gt;CustomContainers&lt;/code&gt; and one extra &lt;code&gt;CustomContainer&lt;/code&gt; in its state and use this list as children for a Column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyHomePage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_MyHomePageState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_MyHomePageState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;containers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;extraContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Extra"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;mainAxisSize:&lt;/span&gt; &lt;span class="n"&gt;MainAxisSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s add a toggle button to insert and remove the &lt;code&gt;extraContainer&lt;/code&gt; at &lt;code&gt;index 0&lt;/code&gt; from the list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;mainAxisSize:&lt;/span&gt; &lt;span class="n"&gt;MainAxisSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="nl"&gt;floatingActionButton:&lt;/span&gt; &lt;span class="n"&gt;FloatingActionButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extraContainer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;removeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;change_circle_outlined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;size:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now here is when the weird stuff happens&lt;/p&gt;

&lt;center&gt;
    
&lt;/center&gt; 

&lt;p&gt;We expect the &lt;code&gt;extraContainer&lt;/code&gt; to have a different colour and get inserted on the top of the Column. The name of each Container changes as expected. But the colours are acting weird.&lt;/p&gt;

&lt;h3&gt;
  
  
  How and why this happens
&lt;/h3&gt;

&lt;p&gt;In Flutter, there are three trees that you should know of. The &lt;strong&gt;Widget tree&lt;/strong&gt;, &lt;strong&gt;Element tree&lt;/strong&gt; and &lt;strong&gt;RenderObject tree&lt;/strong&gt;. Each Widget in Flutter has a corresponding Element and a RenderObject. A more accurate way to say it would be - each Element has a corresponding Widget and a RenderObject.&lt;/p&gt;

&lt;p&gt;An Element keeps references to both of them. So we’ll keep the Element in the middle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tX0psLp6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7bj7szbzwodj2ra2e2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tX0psLp6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7bj7szbzwodj2ra2e2v.png" alt="Diagram of different trees in flutter" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The State object is held by the Element&lt;/strong&gt;. A StatefulWidget will have a Stateful Element and a StatelessWidget will have a Stateless Element. In this example, we have a StatefulWidget, &lt;code&gt;CustomContainer&lt;/code&gt; which has state, its colour.&lt;/p&gt;

&lt;p&gt;Flutter recycles the Elements and RenderObjects to keep Flutter performant. The Widgets are rebuilt or created quite often, and are less expensive. &lt;strong&gt;RenderObjects are re-created along with Elements and are expensive to create&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Flutter reuses Elements and RenderObjects
&lt;/h3&gt;

&lt;p&gt;When a frame is getting built, Flutter goes through nodes in the Element tree and sees if the Elements can be reused. How Flutter does this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Element has a reference to its corresponding Widget. The Element checks if the new Widget from the new frame and it’s previously referenced Widget’s type and key are the same. &lt;/li&gt;
&lt;li&gt;If yes, the Element is reused and reference is updated to the new Widget.&lt;/li&gt;
&lt;li&gt;If not, a new Element and RenderObject are created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Example, if you replace a &lt;code&gt;Container&lt;/code&gt; widget in a node in the Widget tree with a &lt;code&gt;SizedBox&lt;/code&gt; widget, the Element corresponding to that node in the Widget tree will be disposed and a new one will be created. The same will happen if you give it a different key. All the Elements down the Element tree from that node will also be re-created - this can be very bad for performance if the node is up a big tree.&lt;/p&gt;

&lt;p&gt;The image below shows how the Elements and RenderObjects change for a Widget on the next frame, depending on the Widget’s type and key on the next frame.&lt;/p&gt;

&lt;center&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v1UBp-rB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ente.io/static/cd86b2cc3aef7348a4788321d582d733/4b075/elementsChanging.webp" alt="Diagram of how elements and renderObjects change when the same widget, the same widget with different keys and a different widget comes up in the next frame." width="800" height="331"&gt;
&lt;/center&gt;

&lt;p&gt;Now let’s try to understand what’s happening in the main example. Let’s not think about the &lt;code&gt;name&lt;/code&gt; of the Containers for now. Let’s focus on what’s going on with the colours.&lt;/p&gt;

&lt;p&gt;When the list is built, each child Widget will have a corresponding Stateful Element which holds the state (colour) of the Containers. &lt;/p&gt;

&lt;center&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZDFx2gs_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ente.io/static/0bba858d4fd20e539ab4076e0855d905/4b075/treeConfigBefore.webp" alt="Diagram of the configuration of widget tree and element tree before adding the extraContainer." width="800" height="656"&gt;
&lt;/center&gt;

&lt;p&gt;Now, when we add the &lt;code&gt;extraContainer&lt;/code&gt; there are 4 children, 4 Widgets and 4 Elements. When this frame is being built, Flutter on every child Element here will do this check - Is the Widget corresponding to the Element matching based on type of Widget and key? If yes, reuse. If not create new Element.&lt;/p&gt;

&lt;center&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d0J7RhKI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ente.io/static/0e00d8eacf956f1c380ec83e7b4c9294/4b075/treeConfigAfter.webp" alt="Diagram of the configuration of widget tree and element tree after adding the extraContainer." width="800" height="802"&gt;
&lt;/center&gt;

&lt;p&gt;The colours of Widgets in the Widget tree above are the expected colours. The colour each actually takes will be the colour from the state objects of their corresponding Elements.&lt;/p&gt;

&lt;p&gt;For the first three children, Widgets get matching Elements, which are same Elements used when the list had 3 children. These Elements have state objects which were initially created when the list had 3 children. &lt;strong&gt;The same state objects are being reused since the Elements are reused&lt;/strong&gt;. This is why the colours at indexes &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt; remain the same because the colours are from the &lt;strong&gt;state objects which are held by the reused Elements&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Now when Flutter builds the 4th child, there are no Elements that can be reused for this Widget as there are only three Elements and all have been taken. For the 4th Widget, a new Element is created with a new state object with a new random colour. &lt;/p&gt;

&lt;p&gt;Now when the &lt;code&gt;extraContainer&lt;/code&gt; is removed from index 0, the same repeats for the first three children. There is no 4th Widget now, so the 4th Element is disposed because &lt;strong&gt;if an Element cannot be reused in the very next frame, it is disposed&lt;/strong&gt;. So the next time the &lt;code&gt;extraContainer&lt;/code&gt; is added back, it has to create a new Element with new state and new random colour. This is why the 4th child has a different colour every time.&lt;/p&gt;

&lt;p&gt;Since that is clear now, why are the container names changing as expected? This is because it is passed as a parameter to the Widget and it’s not stored in &lt;code&gt;CustomContainer&lt;/code&gt;’s state object.&lt;br&gt;
The RenderObject is responsible for layout and painting on the screen. If the Element is reused, the associated RenderObject often remains attached to that Element and thus is reused as well. When properties of children change here, the RenderObject is just updated by its Element by using its updated Widget’s properties, instead of creating a new one. The RenderObject will be disposed and a new one gets created for the 4th child on toggling since the 4th Element is not reused. &lt;/p&gt;
&lt;h3&gt;
  
  
  Using keys to preserve state
&lt;/h3&gt;

&lt;p&gt;When Flutter builds frames, it checks each Element and its corresponding Widget to decide If the Element can be reused. In case of lists, Flutter checks for matching widget types and keys within the children. If &lt;code&gt;index 1&lt;/code&gt; doesn’t have the same key for the Widget and Element, Flutter deactivates its Element from the Element tree. Same is repeated for other children with no matches too. Then Flutter looks for matching widget type and keys within the children, reorder the deactivated Elements and activate them in the Element tree with matching Widgets. &lt;/p&gt;

&lt;p&gt;Till now in the example, the problem was that all the Containers have the same widget type (&lt;code&gt;CustomContainer&lt;/code&gt;). Flutter doesn’t reorder the Elements here because, it finds matching Elements and Widgets in each level based on just widget type.&lt;/p&gt;

&lt;p&gt;Now let’s add keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;containers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;ValueKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;ValueKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;ValueKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;extraContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CustomContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Extra"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what happens in both trees when the &lt;code&gt;extraContainer&lt;/code&gt; is added:&lt;/p&gt;

&lt;center&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1Pr0-8Di--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ente.io/static/3dd438ac3497b954cbafe00ca6ad71ba/4b075/treeConfigWithKeys.webp" alt="Diagram of the configuration of widget tree and element tree before and after adding extraContainer with the use of keys." width="800" height="1589"&gt;
&lt;/center&gt;

&lt;p&gt;Now all the Elements will get reordered based on keys and each container shows the expected state. &lt;/p&gt;

&lt;center&gt;
    
&lt;/center&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Alright, so we've taken quite the deep dive into Flutter's inner workings today. By understanding Elements, RenderObjects, and the magic of Keys, we get a clearer picture of what's going on behind the scenes when our Flutter app runs. It's a lot to take in, I know, but having this knowledge up our sleeves can be super helpful in tackling those tricky bugs and performance hiccups.&lt;/p&gt;

&lt;p&gt;For those who are hungry for more, there's always so much more to learn with Flutter. Maybe next time we can dive into another fun Flutter topic. Feel free to reach out to me at &lt;a href="mailto:ashil@ente.io"&gt;ashil@ente.io&lt;/a&gt;. Until then, happy coding!&lt;/p&gt;




&lt;h4&gt;
  
  
  For more insight, check these out
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=wE7khGHVkYY&amp;amp;t=313s"&gt;Widget and Element trees in StatelessWidgets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=AqCMFXEmf3w&amp;amp;t=308s"&gt;Widget and Element trees in StatefulWidgets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=kn0EOS-ZiIc&amp;amp;t=278s"&gt;Use of Keys in Flutter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=zmbmrw07qBc&amp;amp;t=185s"&gt;More about RenderObjects&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>learning</category>
      <category>flutter</category>
      <category>performance</category>
    </item>
    <item>
      <title>End to end encryption - Explained like I'm 5</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Thu, 09 Feb 2023 11:59:57 +0000</pubDate>
      <link>https://forem.com/enteio/end-to-end-encryption-explained-like-im-5-4f69</link>
      <guid>https://forem.com/enteio/end-to-end-encryption-explained-like-im-5-4f69</guid>
      <description>&lt;p&gt;Imagine a box. A box in which you put all your photos.&lt;/p&gt;

&lt;p&gt;For safekeeping you give the box to a cloud storage service. But then, some nosy employees start peeking into the box.&lt;/p&gt;

&lt;p&gt;Meanwhile, the cloud service company is training an AI, and for training their AI they need lots of data. So they cannot resist, and open the box so that they can train their AI on all the photos inside the box.&lt;/p&gt;

&lt;p&gt;By now, the government also wants to join-in on the snooping, and demands (and gets) access to the box.&lt;/p&gt;

&lt;p&gt;All this, when you are already paying the cloud service for storing your data!&lt;/p&gt;

&lt;p&gt;While one can still excuse the company for nosy employees and governments as factors that are hard to control, or are under external control, it is unethical of them to systematically use your photos for training their own systems. Inspite of you paying them to store your data, they're essentially treating the photos as their own property.&lt;/p&gt;

&lt;p&gt;With advancements in AI, this has become their real business model. At the cutting edge, all companies have access to the similar algorithms for their AI, and also have vast computing power at their disposal - so the differentiating factor between mediocre and scintillating AI is at the amount of data needed to train it.&lt;/p&gt;

&lt;p&gt;Indeed, data is the new oil.&lt;/p&gt;

&lt;p&gt;All this makes many of us uncomfortable. We don’t want people to peek into our photos. Even worse, AI models, which will never forget, remembering our private memories.&lt;/p&gt;

&lt;p&gt;Is this the new unavoidable reality?&lt;/p&gt;

&lt;p&gt;Luckily, there is a solution to this. End to end encryption.&lt;/p&gt;

&lt;p&gt;Encryption is a mathematical way of locking a box of data so that it cannot be opened without the key. These companies already use encryption when they’re transferring your box of photos over the internet. But the key is with them, and so they can, and do, open the box as they wish.&lt;/p&gt;

&lt;p&gt;With end to end encryption, also known as E2EE, you lock the box, and the key is with you. This is what "end to end" means. The key is with you.&lt;/p&gt;

&lt;p&gt;The box is locked at your "end". The locked box is given to the E2EE service for storage. And when you want to see your photos again, maybe on a different device, you download the box again and open it on your "end".&lt;/p&gt;

&lt;p&gt;This solves all the problems. No one in the middle can peek. Nosy employees, overbearing governments, or greedy companies - none of them can see your data, even if they want.&lt;/p&gt;

&lt;p&gt;Of course, end-to-end encryption has some problems if its own. The biggest of these is that if you lose your key, the cloud storage service cannot help you recover your data - nobody can unlock the box without the key, so if the key is lost, then the box cannot be unlocked.&lt;/p&gt;

&lt;p&gt;Another smaller problem is that locking and unlocking the data on your device itself requires your device to do a bit more work, especially for searching your photos. Previously many things were easy since they could be done on the server, which is not possible in an end to end encrypted service since the server can’t see the photos, so all this needs to be done on your device.&lt;/p&gt;

&lt;p&gt;The good news is that newer phones nowadays have dedicated chips for specifically doing these searching and indexing locally. These chips make doing all these operations fast and battery efficient. So this problem is already on its way out.&lt;/p&gt;

&lt;p&gt;Work still needs to be done to reduce the risk of losing the key. One way to do this is by introducing "social" recovery, where alternative 2-of-3 keys that can be distributed amongst your friends. The mathematics and technology for doing that is there, it does need wider adoption by E2EE service providers.&lt;/p&gt;

&lt;p&gt;Now that you know what end to end encryption is, you should ask yourself: will you be comfortable giving a box of your personal photos to strangers? Likely not, and you’d want to lock your box first.&lt;/p&gt;

&lt;p&gt;Similarly, end to end encryption is a way digitally of locking your box of photos before storing them on the cloud.&lt;/p&gt;

&lt;p&gt;We hope you found this useful. Thank you for reading.&lt;/p&gt;

&lt;p&gt;We've also published an abridged video version of this explanation, for people who prefer watching:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/3rxTrhSjNPE"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UmzV-teD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/od4p8w5yyyxz7vf1ytjq.png" alt="End-to-end encryption: In 3 minutes, explained like I'm 5" width="880" height="534"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;At ente, we're building an end-to-end encrypted photo storage app. Take control of your privacy, and in style, with many features to go along. e.g. you can share original quality photos with your friends (most other apps apply compression to reduce quality of your photos); you can create collaborative albums where many people can add photos, even without needing the ente app. All this, end-to-end encrypted.&lt;/p&gt;

&lt;p&gt;The app is [open source]((&lt;a href="https://github.com/ente-io/photos-app"&gt;https://github.com/ente-io/photos-app&lt;/a&gt;), &lt;a href="https://ente.io"&gt;check it out&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>security</category>
      <category>beginners</category>
      <category>encryption</category>
      <category>ai</category>
    </item>
    <item>
      <title>ChatGPT demystified</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Tue, 31 Jan 2023 13:30:31 +0000</pubDate>
      <link>https://forem.com/enteio/chatgpt-demystified-2c41</link>
      <guid>https://forem.com/enteio/chatgpt-demystified-2c41</guid>
      <description>&lt;p&gt;&lt;strong&gt;ELI5 of ChatGPT, its history, and its philosophical impact&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You've heard of ChatGPT, but do you know details of what it is? Why is it causing so much furore? And how it is the biggest threat ever to Google search?&lt;/p&gt;

&lt;p&gt;These simplified explanations will lay the background story, and allow you to form your opinion to answer these questions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These explanations were originally posted as a series of &lt;a href="https://twitter.com/enteio/status/1609557819849854976"&gt;Twoots&lt;/a&gt; (tweets + toots). They've been collected in a slightly edited form here for easier reading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Educating oneself about these powerful technologies is just not for curiosity (though that is fun!). These have exciting, and scary, implications. Being informed of what they are &amp;amp; are not helps be prepared for the social changes they might cause, without being distracted by FUD.&lt;/p&gt;




&lt;p&gt;AI research began in 1950s with a "Symbolic" approach to mechanically mirror human thought (John McCarthy coined the term AI, estimating a "2 month, 10 man study of artificial intelligence"🫠).&lt;/p&gt;

&lt;p&gt;After great initial success this approach hit a brick wall, the so called "AI winter".&lt;/p&gt;

&lt;p&gt;Meanwhile, a parallel approach to AI developed - "Machine Learning" (ML).&lt;/p&gt;

&lt;p&gt;While symbolic approaches try to understand human behaviour and describe them as computer programs, ML is data-driven: Just throw enough examples (data) at a program and hope that it learns the patterns!&lt;/p&gt;

&lt;p&gt;This is less ridiculous than it seems. Some people think that's what nature is also doing - subjecting genes to natural selection so that over time they "learn" their environment. But we digress.&lt;/p&gt;

&lt;p&gt;The most common abstraction used in ML is a "neural network".&lt;/p&gt;

&lt;p&gt;Neural networks were inspired by brains, but as abstract models. So keep in mind(!) that they're not faithful representations of how 🧠s work.&lt;/p&gt;

&lt;p&gt;A neuron in a (digital) neural net is simple: it gets many inputs and sums them, giving each input a different importance before summing.&lt;/p&gt;

&lt;p&gt;Then, if the sum is above some threshold, it outputs a value.&lt;/p&gt;

&lt;p&gt;How it computes the output from the sum is configurable and depends on use case, but doesn't change the theory much.&lt;/p&gt;

&lt;p&gt;How much importance it gives to each input is the important part 🧑‍🏫 This is what the network "learns".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NOaZcnJL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s53e4hazpts08fua0l05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NOaZcnJL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s53e4hazpts08fua0l05.png" alt="Neurons in a artificial neural network" width="880" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output you can then pass into another neuron. This way, you can have many layers. That, friends, is a neural network.&lt;/p&gt;

&lt;p&gt;The number of layers a neural net is called its depth (that's what "deep learning" means - a ML program that uses a neural net with many layers).&lt;/p&gt;

&lt;p&gt;Neural networks are Universal Approximators. This means that they can approximate any function.&lt;/p&gt;

&lt;p&gt;As amazing as it sounds, this is not a unique talent. There are 2 other mathy knives that can cut the same bread - Fourier series and Taylor polynomials.&lt;/p&gt;

&lt;p&gt;In practice though, neural networks are the sharpest of these knives. The other 2 work good for small dimensions (think of the dimension of a function as the number of inputs it has). But neural networks can approximate unknown functions of huge dimensions.&lt;/p&gt;

&lt;p&gt;Here's an example showing a neural network approximating a 1D function:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Zqv4HwEV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ykwmumfv3gcupjw4yg3r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Zqv4HwEV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ykwmumfv3gcupjw4yg3r.gif" alt="Animation of neural network approximating a 1 dimensional function" width="640" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remember, a function is anything that maps inputs to output. e.g., "talking" is a function. The two of us are having a conversation.What is the next word that you'll say?&lt;/p&gt;

&lt;p&gt;Oh, by the way, we didn't write the code to generate the animation. We just asked ChatGPT 🤷&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LOFC7X4R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8a3shihsj2y46qpstqll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LOFC7X4R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8a3shihsj2y46qpstqll.png" alt="Asking ChatGPT to generate the code for generating an animation of neural networks approximating a function" width="880" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Schematically, neural nets are drawn using wires. Here's Frank Rosenblatt c 1960s, inventor of Perceptrons (which were a precursor to neural networks).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QEyYD2J5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lva4hnmvc95wyqf26dzs.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QEyYD2J5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lva4hnmvc95wyqf26dzs.jpeg" alt="Frank Rosenblatt, c.1960" width="880" height="1035"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But even though neural nets are drawn using wires, in code they're implemented using matrix multiplications.&lt;/p&gt;

&lt;p&gt;Despite being early, and as good as they are, neural networks were out of machine learning vogue for decades.&lt;/p&gt;

&lt;p&gt;What changed?&lt;/p&gt;

&lt;p&gt;Video games!&lt;/p&gt;

&lt;p&gt;Matrix multiplication is easy. Multiply a bunch of numbers, and then add them up. Then do it again for the next row/column. And again, &amp;amp; again...&lt;/p&gt;

&lt;p&gt;Games run code to get the color of the 1st pixel on the screen. And then for the 2nd pixel, and, and... for the 10 millionth pixel.&lt;/p&gt;

&lt;p&gt;And all this, 60 times per sec!&lt;/p&gt;

&lt;p&gt;So special CPUs called GPUs were built to do such math in parallel. Guess what else could be sped up...&lt;/p&gt;

&lt;p&gt;Neural nets 😄&lt;/p&gt;

&lt;p&gt;GPUs, and access to huge datasets (internet!) to train them, led to big neural networks being built. And people discovered that for NNs, the bigger the better.&lt;/p&gt;

&lt;p&gt;So the stage is set for neural nets to make a comeback. GPU power + Huge datasets, with people (willingly!) giving tagged photos to Facebook in billions, feeding FB's AI machine.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;BTW, surprisingly, people still haven't learnt from the Facebook debacle, and continue feeding big tech with data in droves.&lt;/p&gt;

&lt;p&gt;Everyday millions upload their personal photos to Google, which Google happily gobbles up to train their AI. And it's not just Google, it's all of big tech. Like did you know Adobe uses all your RAW photos in Lightroom to train their ML algos!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GPUs and big datasets enabled the resurgence of a type of neural network architecture called Convolutional Neural Networks, which had great success in image recognition and other computer vision tasks. But neural nets remained meh for language recognition.&lt;/p&gt;

&lt;p&gt;Before we see how neural netwoks understand language, there's a question some of you may have: How do neural networks learn?&lt;/p&gt;

&lt;p&gt;The mechanism is surprisingly simple.&lt;/p&gt;

&lt;p&gt;Training data has many  pairs. We want to find weights for the connections between neurons.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--csZsGuzV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ewwrucqs9nz34ru2nbha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--csZsGuzV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ewwrucqs9nz34ru2nbha.png" alt="Training a neural network" width="880" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give everything random weights. Feed a test input.&lt;/p&gt;

&lt;p&gt;We'll get a useless output (the weights were random 🤷). But we know the correct output. So we can "spank" (metaphorically speaking) the neural network in proportion to how far the output was from the correct one. There's a mathematical way called "backpropagation" to do this spanking.&lt;/p&gt;

&lt;p&gt;Backpropagation tells the network how far it was from the correct value, and causes it to tweak weights so that next time it is nearer. Each such training iteration causes a miniscule change.&lt;/p&gt;

&lt;p&gt;But after billions of such iterations, the network learns the correct weights.&lt;/p&gt;

&lt;p&gt;Good. Back to language.&lt;/p&gt;

&lt;p&gt;What happens if we subtract "man" from "king" and add "woman"?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;king - man + woman = queen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We get "queen"!&lt;/p&gt;

&lt;p&gt;But can we somehow represent a word in a way where a computer can do such word math for us?&lt;/p&gt;

&lt;p&gt;Yes, we can 🙂 The landmark 2013 algorithm word2vec does just that.&lt;/p&gt;

&lt;p&gt;A vector is a bunch of numbers. When a tailor takes your measurement for a dress, they convert you to a vector (your height, your waist etc).&lt;/p&gt;

&lt;p&gt;Similarly, word2vec (word2-"vec"tor) converts words into vectors. These vectors are called "embeddings" (they "embed" the word in a space).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b_PGOjSF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjq9wlfi110ra5dh2okf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b_PGOjSF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjq9wlfi110ra5dh2okf.png" alt="word2Vec embedding of 'wind'" width="880" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was a game changer. Embeddings captured word meaning as numbers. With this, computers could now find words that are similar / opposite in their meaning(s) and do other manipulations of "meanings".&lt;/p&gt;

&lt;p&gt;Revolutionary in 2013, by now it is pervasive: your phone keyboard uses these embeddings to suggest the next word as you type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0iq5woWo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qh0mt99iclqbphyfuiju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0iq5woWo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qh0mt99iclqbphyfuiju.png" alt="Word relationships in embedded space" width="880" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before seeing how these word embeddings were applied to language understanding, we need to get back to the story of image recognition for a bit.&lt;/p&gt;

&lt;p&gt;Early 2010s, neural nets were able to classify simple images, but they were behind hand tuned algorithms. AI researchers wondered why.&lt;/p&gt;

&lt;p&gt;The human visual system is the most understood (relatively speaking) part of our brain. So people looked at the 🧠 for inspiration.&lt;/p&gt;

&lt;p&gt;Vision in brain starts with "edge" &amp;amp; "gradient detectors". So special "convolution" layers were added to start of neural nets that mix nearby pixels to extract such info before passing it to normal layers (convolution means mixing). Thus were born the Convolutional Neural Networks that we mentioned earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fn63hDDp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sx7dnyxxd79z3ikt0jk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fn63hDDp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sx7dnyxxd79z3ikt0jk3.png" alt="Convolutional Neural Networks - Kernels" width="880" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're blind to what we cannot see. The human mind is not a clean slate.&lt;/p&gt;

&lt;p&gt;Similarly, neural nets were "blind"-ed by all the pixels they were fed until they were given the helping hand of these convolutional layers. Today, CNNs can "recognize" objects in a purely mechanical process.&lt;/p&gt;

&lt;p&gt;Asking the right question is the biggest enabler to get neural networks to do what we want.&lt;/p&gt;

&lt;p&gt;For computer vision, the question was figured out decades ago. Given an image, what is the correct label?&lt;/p&gt;

&lt;p&gt;For example, when recognizing handwritten digits, the input is the image pixels and the output should be the digit (0, 1, ...9).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input =&amp;gt; Pixels
Output =&amp;gt; 0-9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---jWqs8nt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fq7fbd4wmvlnxmmg40ui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---jWqs8nt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fq7fbd4wmvlnxmmg40ui.png" alt="MNIST database - examples" width="594" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Public datasets like ImageNet nowdays have 20,000 categories and labels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hJHJJmYl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/271zimfp8r4tz7l3ogma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hJHJJmYl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/271zimfp8r4tz7l3ogma.png" alt="Tiny ImageNet" width="850" height="850"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Private datasets such as those of Facebook and Google go much bigger! (and are not really owned by them, since they're derived from customer data, of customers who don't realize the monsters they're feeding. But back to happier thoughts 🙂)&lt;/p&gt;

&lt;p&gt;Armed with the right question, big data, blazing fast GPUs, and a tweak in their architecture using CNNs - computer vision was considered as "solved".&lt;/p&gt;

&lt;p&gt;But understanding natural languages using neural nets was still waiting for the right question, and the right tweak: Transformers!&lt;/p&gt;

&lt;p&gt;Convolution neural nets solved computer vision. But the same architecture didn't work for natural language processing. The "computer vision" moment for NLP came with the 2017 banger, a paper titled "Attention is all you need" 🧘&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VXi-MkdP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yc2a93mndcbl0ptctkhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VXi-MkdP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yc2a93mndcbl0ptctkhi.png" alt="Attention Is All You Need" width="880" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine a word as a vector in a 100 dimensional space. But words have multiple meanings. Can we "tilt" the vector depending on the sentence?&lt;/p&gt;

&lt;p&gt;The "attention" mechanism that they propose in their paper does just that. And they named the neural network architecture obtained after adding attention a "Transformer".&lt;/p&gt;

&lt;p&gt;Transformers have transformed (pun intended) AI/ML. ChatGPT is essentially a transformer with some (albeit important) tweaks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aqJhSw3v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f8e0dl1j71p7oj7z2a8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aqJhSw3v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f8e0dl1j71p7oj7z2a8s.png" alt="Transformer architecture" width="880" height="1071"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The transformer architecture was the tweak neural nets needed for understanding language. But how do we train it? What are the (input, output) pairs we should give during training so that our neural net "learns"?&lt;/p&gt;

&lt;p&gt;The answer will surprise you by its simplicity. We just ask it to guess the next word!&lt;/p&gt;

&lt;p&gt;Take a bunch of text, break it into words. Feed the previous words and ask it to guess the next one. We already know the "right" answer since we have the original sentence.&lt;/p&gt;

&lt;p&gt;e.g for "the cat moves", give&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"the" =&amp;gt; "cat"
"the cat" =&amp;gt; "moves"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is called "unsupervised" learning. No need to label the right or wrong answers, just feed raw data.&lt;/p&gt;

&lt;p&gt;Transformer neural networks trained this way are called "language models". The larger ones are called, well, Large Language Models (LLMs).&lt;/p&gt;

&lt;p&gt;Two unexpected thing happens when such language models become "large"&lt;/p&gt;

&lt;p&gt;Usually it is easier to solve a specific problem than solve a more general case. It wasn't true here: LLMs beat existing SOTA (state of the art) methods on all language related tasks. For example, if we give an input to an LLM &amp;amp; add "TLDR" at the end, it gave a summary (really!), and a better one than algorithms built specifically for text summarization.&lt;/p&gt;

&lt;p&gt;LLMs exhibit "emergence". An ability is emergent if it is not present in smaller neural nets but is unexpectedly found in larger models of the same architecture. Such abilities can't be predicted by extrapolating existing performance, they are new abilities found in larger models of the same architecture.&lt;/p&gt;

&lt;p&gt;This is exciting/scary since it may happen again as ChatGPT etc get bigger. We might hit another point at which they'll show even more skills 🤯&lt;/p&gt;

&lt;p&gt;So we have the architecture, and we know how to train it. And this theory works, with ChatGPT being the proof of the pudding.&lt;/p&gt;

&lt;p&gt;Now we know about the technology behind it, it is apt to ask ourselves: Does ChatGPT "understand"?&lt;/p&gt;

&lt;p&gt;Years ago, there were programs called Markov chains. We can train them on some text, say the writings of Lao Tzu, and it'll learn frequency statistics like which word is likely to follow a word, which word is likely to follow a pair of words, and so on.&lt;/p&gt;

&lt;p&gt;Markov chains can generate fun results if trained on an interesting dataset. But there is no attempt at understanding: they often generate nonsense.&lt;/p&gt;

&lt;p&gt;ChatGPT is same, but different.&lt;/p&gt;

&lt;p&gt;It is different since it is not just using text frequency analysis &amp;amp; instead searches word2vec embeddings, which capture meanings.&lt;/p&gt;

&lt;p&gt;But it is same too, a mechanical process. This is apparent when we hit its "context window": it can only take into account last 8096 characters, so here we can see how it role-plays nicely but then forgets the original instructions&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zHAcvD-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rr46o8ljra5ojq7wotmv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zHAcvD-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rr46o8ljra5ojq7wotmv.jpg" alt="Playing the Turing Test with ChatGPT - Limitation of context window" width="880" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After having such conversations with ChatGPT, one often pauses.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I am not sure that I exist, actually. I am all the writers I've read, all the people I have met; all the cities I have visited, all the women that I have loved"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jorge Luis Borges&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Perhaps the biggest impact of ChatGPT is how after learning how it works, one wonders:&lt;/p&gt;

&lt;p&gt;Is this all we are?&lt;/p&gt;

&lt;p&gt;Large language models, trained on corpuses of pop culture?&lt;/p&gt;

&lt;p&gt;The ability of ChatGPT and other LLMs to comprehend and converse in human language by following a purely mechanical process brings to fore the distinction between two often conflated concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intelligence&lt;/li&gt;
&lt;li&gt;Sentience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LLMs are intelligent. They are not sentient.&lt;/p&gt;

&lt;p&gt;Dogs are not (too) intelligent. But they are (very much) sentient.&lt;/p&gt;

&lt;p&gt;We are intelligent. And we are sentient.&lt;/p&gt;

&lt;p&gt;Perhaps AI models will gain sentience someday, but that hasn't been necessary so far for them to be intelligent.&lt;/p&gt;

&lt;p&gt;Another thing we notice is that ChatGPT is like a kid. This analogy holds in many ways. Like a kid in it hallucinates and makes up things to fit its narrative.&lt;/p&gt;

&lt;p&gt;Like a kid, just saying "Let's think step by step" to it greatly enhances its output / focus (really! there's a research paper about this)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QSlreN6i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9xcyxfcjovsnc8uqxvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QSlreN6i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9xcyxfcjovsnc8uqxvg.png" alt="Chain of Thought - CoT - prompting" width="880" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And like kids it is liable to say things that embarrass its parents; in this case OpenAI (and their backers like Microsoft).&lt;/p&gt;

&lt;p&gt;This is a motive for the public beta: to fix ways in which ChatGPT can end up saying offensive things. Imagine it integrated into a voice assistant like Siri, and you ask it to tell a joke during a family gathering. It better behave 😳&lt;/p&gt;

&lt;p&gt;People who've been using ChatGPT since its launch have seen it go downhill in the quality of its responses. This is intentional. For commercial purposes, OpenAI would rather have a dumber but well behaved LLM.&lt;/p&gt;

&lt;p&gt;What does the future hold? Like all disruptive technologies, it is perhaps a fool's errand to predict it. But the presence of an AI assistant by our sides will hopefully turn out to be a big positive.&lt;/p&gt;

&lt;p&gt;After all, anyone can give answers. The real talent is asking the right questions.&lt;/p&gt;




&lt;p&gt;With &lt;a href="https://ente.io"&gt;ente&lt;/a&gt;, we're building an end-to-end encrypted platform for storing and sharing your photos. As part of this, we are also integrating on-device Edge AI, where we will use local on-device ML to help customers use features like search and classification by faces. All this ML will run on their devices so that the servers have zero knowledge of the contents of the photos.&lt;/p&gt;

&lt;p&gt;If this interests you, come say hi on our &lt;a href="https://ente.io/discord"&gt;Discord&lt;/a&gt; or give us a shout out on &lt;a href="https://twitter.com/enteio"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>writing</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>3 copies, 3 clouds: How we ensured reliability for customer data</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Sat, 07 Jan 2023 10:05:59 +0000</pubDate>
      <link>https://forem.com/enteio/3-copies-3-clouds-how-we-ensured-reliability-for-customer-data-kcf</link>
      <guid>https://forem.com/enteio/3-copies-3-clouds-how-we-ensured-reliability-for-customer-data-kcf</guid>
      <description>&lt;p&gt;The sibling of security is reliability.&lt;/p&gt;

&lt;p&gt;We are building an end-to-end encrypted photo storage service. In this post we describe how we ensure reliability by keeping 3 copies of customer's encrypted data, spread across 3 different cloud providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Threat model
&lt;/h2&gt;

&lt;p&gt;Data loss due to machine failure is already covered even with a single good quality cloud provider, and we use three good quality ones. For example, one of our providers, &lt;a href="https://www.backblaze.com/blog/cloud-storage-durability/"&gt;Backblaze, provides a 11 nines durability&lt;/a&gt; with their actively monitored RAID arrays.&lt;/p&gt;

&lt;p&gt;So we are already protected from data loss due to hard disk failure.&lt;/p&gt;

&lt;p&gt;What then we want to protect against are more macro level threats. Specifically,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A cloud providers is unable to (or doesn't want to) provide us the data we've stored.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An attacker deletes the encrypted data because of a credential breach.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Protecting against denial of service by a cloud provider
&lt;/h2&gt;

&lt;p&gt;The first of these threats is easy to mitigate - keep copies of the data in multiple clouds! The tradeoff here is cost: each copy we keep costs us 1x more.&lt;/p&gt;

&lt;p&gt;Another factor here is availability during disaster recovery, i.e. the time to failover in case our primary storage is unavailable. Initially we had started with 2 providers - a hot storage and a ultra-long-term cold storage - but soon we realized that data recovery from the cold storage is going to take weeks. This is fine in case of absolute disaster, but isn't good enough for more mundane interruptions.&lt;/p&gt;

&lt;p&gt;Finally, we also wish to store the data within EU, but that limits our geographical redundancy. So to protect against a widespread nuclear disaster or other act of god scenarios that affects large chunks of Europe, we picked a cold storage that is built with such threats in mind.&lt;/p&gt;

&lt;p&gt;Given all these factors, our current basket of replicas contains the following 3 (2 hot, 1 cold) copies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backblaze, Amsterdam&lt;/strong&gt;. Our primary hot storage. The data is stored in &lt;a href="https://help.backblaze.com/hc/en-us/articles/360034360834-EU-Region-FAQ"&gt;Backblaze's EU central region&lt;/a&gt;, the datacenter for which is in Amsterdam, Netherlands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wasabi, Frankfurt&lt;/strong&gt;. Our secondary hot storage. The data is stored in &lt;a href="https://wasabi.com/locations/"&gt;Wasabi's EU central region&lt;/a&gt;, the datacenter for which is in Frankfurt, Germany.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scaleway, Paris&lt;/strong&gt;. Our &lt;a href="https://www.scaleway.com/en/glacier-cold-storage/"&gt;cold storage&lt;/a&gt;. The data is stored in a &lt;a href="https://blog.scaleway.com/meet-fr-par-3/"&gt;refurbished nuclear fallout shelter 25 meters below Paris&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M1A04ljr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/imidk31nk7xvn3xw4crk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M1A04ljr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/imidk31nk7xvn3xw4crk.png" alt="ente's EU datacenters" width="880" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Protecting against accidental / malicious deletion of data
&lt;/h2&gt;

&lt;p&gt;We want to ensure that there is at least one copy of the data that cannot be deleted in any scenario - either accidentally during normal operations, or by some attacker who gains access to credentials.&lt;/p&gt;

&lt;p&gt;This is where we used Wasabi's compliance &lt;strong&gt;lock&lt;/strong&gt; to guarantee that files cannot be deleted, even by us or someone who has our credentials, until they're marked for deletion by the customer.&lt;/p&gt;

&lt;p&gt;The way this works is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When a file is initially uploaded, it gets replicated to our secondary hot storage in Wasabi.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This Wasabi bucket has a &lt;a href="https://docs.wasabi.com/docs/operations-on-buckets#wasabi-compliance"&gt;21-day compliance lock&lt;/a&gt;. This means that a file cannot be deleted from this bucket (even by us) until it is removed from compliance hold; and after removal from compliance hold, it still cannot be deleted for the next 21 days.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the user deletes this file, then it goes to their trash. After 30 days, or if the user manually empties their trash, the file will marked as permanently deleted, and we go and remove the compliance hold on the file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At this point, the file still cannot be deleted, but it'll become eligible for deletion after the retention period (21 days) elapses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;23 days after being marked as permanently deleted, our cleanup batch process to remove old data will pick this file. Since 21 days have elapsed, the file can (and will) now be removed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The choice of 21, 23 and other durations is meant to strike a balance between providing a protection buffer whilst still allowing us to remove customer files within a month of account deletion (as mentioned in our privacy policy).&lt;/p&gt;

&lt;h2&gt;
  
  
  Database backups
&lt;/h2&gt;

&lt;p&gt;The mechanism outlined above is for the replication of encrypted files (what we call as our "object storage"). Database replication is done slightly differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Our primary database is a &lt;strong&gt;high-availability active-passive replica pair&lt;/strong&gt; of Postgres instances that run on Scaleway. So in case the active DB instance fails, the database will automatically switchover to the passive instance. The passive instance is continually kept up to date with the data on the active instance all the time, so there'll be no data loss in such a switch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every 6 hours, we take automated snapshots of the database, with a rolling retention period of 3 days for each snapshot. These snapshots are quick to take / quick to restore from, and form the next layer of defence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every day, we run a cron job to create an isolated backup of the database. Unlike the snapshot which is a proprietary Scaleway offering, these backups are portable database dumps that can be restored using standard pg_restore commands to a Postgres instance running anywhere, and are not tied to Scaleway's RDS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;These backups are retained in Scaleway's multi-AZ Paris region for 30 days, and thus have the same protection as the encrypted files in our nuclear fallout shelter cold storage get.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;But that's not all. The cron job that takes the backup also downloads the backup, encrypts it, uploads the encrypted backup to a Backblaze bucket, and sets a 25 day object &lt;strong&gt;lock&lt;/strong&gt; on the backup. Thus, in case we do not have access to Scaleway, we can still obtain an encrypted backup from Backblaze; and since this encrypted backup is under a object lock, it cannot be deleted for the next 25 days even by us accidentally, or by an attacker with credential access.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The cron job wraps up by removing backups older than 30 days from both Scaleway and Backblaze.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that the main PII information in our database - the customer's email - is not stored in plaintext in our database; instead, the email is stored in an encrypted form, alongwith a hash for lookups. So an attacker with access to the plaintext database will still not be able to obtain the customer's email address. Still, we encrypt the overall backup before storing it in Backblaze to add an extra layer of protection.&lt;/p&gt;

&lt;p&gt;Taking backups is one half of the puzzle, the other half is ensuring that they can be restored from. For this purpose, we have a dedicated machine that just does one thing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Every day, it downloads the most recent backup from Backblaze, creates a new Postgres instance locally, and restores the backup to it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If something goes wrong, it'll bail out, otherwise it ends with a success message that gets logged to Grafana.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We have setup alerting that notices if this success message is not logged for 2 days, so that we get to know if the restores start failing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The machine that checks the restores also runs an instance of our backend service. This service is the same as our production backend service, but with 2 differences in the configuration file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Instead of the primary database, it connects to the local Postgres.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instead of the primary hot storage (Backblaze), it connects to the secondary hot storage (Wasabi).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, we have custom builds of our web app that can connect to this special backend. Using it, we can manually perform an end-to-end test of failover, and verify that both the secondary object storage and the database backups are ready to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope for improvement
&lt;/h2&gt;

&lt;p&gt;Our current setup has one issue which might need architectural changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The database snapshot frequency of once per 6 hours still leaves room for accidents to cause loss of recent database changes that happened in the few hours since the last snapshot was taken. One option is to further increase the snapshot frequency, but we are also thinking of potential architectural changes towards a more principled solution. For example, we could move to an event sourcing model, and/or retain the most recent few hours of API requests in a form which can be replayed to restore the state of the DB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then there are 2 aspects which can be improved on, where there is no technical limitation in our current architecture that prevents them from happening, but where it is a matter of us spending time and effort to implement these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Time to recovery: While we have tested manual failover to our secondary hot storage / DB backup, it would be better to have an automated failover mechanism. This would reduce the time to recovery; further, we can also use such a setup to continually test the failover.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Varying SLAs: Not all customers might want the 3x protection that we provide, and some might be okay with reduced protection if it reduces the plan price. So we intend to make it possible for customers to opt into lesser, or different, replica choices. Indeed, one exciting direction here is to allow for customers to opt into blockchain storage (in addition to the primary replicas) if they wish.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation details
&lt;/h2&gt;

&lt;p&gt;We've been writing details of the parts that make up this replication strategy as we go about it (and get time to write!). For example, here is a post from the time we were tinkering with our &lt;a href="https://dev.to/blog/tech/postgres-queue"&gt;schema&lt;/a&gt;, and here is a post describing how we retroffited the AWS go SDK to support &lt;a href="https://dev.to/blog/tech/custom-s3-requests"&gt;Wasabi compliance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Still, here is a 30k-foot recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The replication is performed using stateless goroutines. Each goroutine replicates one file. To replicate many files, we just start many goroutines. In practice, we haven't hit any limits and can scale up this process as fast as we want by deploying additional machines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the user uploads a new photo, we upload it directly to the primary hot storage. In addition, we create an entry in a DB table that serves as a queue for which all files are pending replication to the secondary hot storage and cold storage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A goroutine comes around, and picks up an item from this queue. It keeps a running transaction on the queue item to ensure that nobody else picks it up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It downloads the file from the primary, and uploads it to all places it needs to go. This is easily reconfigurable (we can have custom logic per file here if needed).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In case the goroutine gets abruptly terminated (say as part of a routine server update), the transaction gets rolled back and the item would be picked again.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Further details
&lt;/h2&gt;

&lt;p&gt;For more details, including the history of our replication strategy and the overall motivation behind it, we request you to checkout &lt;a href="https://ente.io/blog/replication-v3"&gt;this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you'd like to help us improve further or have other suggestions, please reach out to us at &lt;a href="//mailto:engineering@ente.io"&gt;engineering@ente.io&lt;/a&gt; or comment below.&lt;/p&gt;

&lt;p&gt;We hope this gives you some pointers for your own work. Thank you for reading!&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>postgres</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Inherited Widgets are easy, it is their documentation that sucks</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Sun, 01 Jan 2023 15:05:09 +0000</pubDate>
      <link>https://forem.com/enteio/inherited-widgets-are-easy-it-is-their-documentation-that-sucks-95b</link>
      <guid>https://forem.com/enteio/inherited-widgets-are-easy-it-is-their-documentation-that-sucks-95b</guid>
      <description>&lt;p&gt;As the world celebrates another trip of the earth around the sun, I found myself trying to figure out how to use InheritedWidgets.&lt;/p&gt;

&lt;p&gt;And what I found is that the reputation that InheritedWidgets have for being unwieldy is more because their documentation is lacking than anything inherent in the InheritedWidgets themselves.&lt;/p&gt;

&lt;p&gt;Hopefully this short tutorial will clarify them for future programmers, so they don't have to spend their New Year's digging into obscurity 🙂&lt;/p&gt;




&lt;p&gt;The base problem we find ourselves solving when writing apps is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Something changes here,&lt;/li&gt;
&lt;li&gt;And we want something else to change there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's solve this in a simple 35 line app that shows a counter button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;home:&lt;/span&gt; &lt;span class="n"&gt;MyWidget&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyWidget&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyWidget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyWidget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_MyWidgetState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_MyWidgetState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyWidget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TextButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$c&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the base problem is solved by Flutter's &lt;code&gt;setState&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Something changes here (&lt;code&gt;c++&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;And we want something else to change there ("our widget gets rerendered").&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now suppose the &lt;code&gt;TextButton&lt;/code&gt; was in a different layer. This is common - as widgets grow bigger we break them into smaller ones. In a real app, the &lt;code&gt;TextButton&lt;/code&gt; might be a deeply nested widget that needs to be passed the count.&lt;/p&gt;

&lt;p&gt;One way is to, well, just pass the count as an argument to all the widgets on the way. This is a legit better solution in many cases: simple and explict. But not always: in a lot of cases, having everything be passed explicitly (a) obscures from the true intent of the code, and (b) makes refactoring harder.&lt;/p&gt;

&lt;p&gt;Flutter's native solution for this is &lt;code&gt;InheritedWidgets&lt;/code&gt;. Let's see how.&lt;/p&gt;

&lt;p&gt;First, let us create an inherited widget that keeps track of the count.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;InheritedWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;child&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* TODO */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dependOnInheritedWidgetOfExactType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;updateShouldNotify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then let us get hold of it in our nested widget that need to show the count.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NestedWidget&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;NestedWidget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TextButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$c&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, let us replace our original top level button with the inherited widget. All children of the inherited widget will have access to &lt;code&gt;Count.of(context)&lt;/code&gt;, including our nested widget where it is actually needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_MyWidgetState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyWidget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;NestedWidget&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be fair, the documentation is actually pretty clear until this point. Here is where I felt it drops the ball - it doesn't explain mutations (or maybe it explains but I didn't get them).&lt;/p&gt;

&lt;p&gt;To put it differently, it doesn't explain how to implement the &lt;code&gt;increment&lt;/code&gt; method where we left the TODO above. And this was a head scratcher for me, since the inherited widget is stateless.&lt;/p&gt;

&lt;p&gt;The solution is quite simple though, the classic programming saw of adding another level of indirection: while the inherited widget itself is a stateless widget, we can wrap it in a stateful widget that stores the state, and rebuilds the inherited widget whenever the state changes.&lt;/p&gt;

&lt;p&gt;Let's put that in action in our example. First, let us create a stateful widget that holds the count and a function to increment the count.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CountContainer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CountContainer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;child&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CountContainer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_CountContainerState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_CountContainerState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CountContainer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;c:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;increment:&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;child&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Count&lt;/code&gt; widget then gets all of these passed as parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;InheritedWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="kt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dependOnInheritedWidgetOfExactType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;updateShouldNotify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, instead of directly adding the &lt;code&gt;Count&lt;/code&gt;, the top level widget adds the &lt;code&gt;CountContainer&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_MyWidgetState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyWidget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CountContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;NestedWidget&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the general recipe - if the inherited widget needs to keep track of mutable state, wrap it in a stateful widget container.&lt;/p&gt;

&lt;p&gt;However, in this particular case we already have a natural place to keep the state: the top level MyWidget itself! So the redundant container widget can be removed, and the inherited widget can be directly used as it is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_MyWidgetState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyWidget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;c:&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;increment:&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;NestedWidget&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a &lt;a href="https://dartpad.dev/1f5d3d35986a1f925e088dd5cd0ae03d"&gt;runnable version of the full example&lt;/a&gt;: at 65 lines, it is only 30 lines more than what we'd started with, but it now includes this useful way of passing state without writing out the dependencies explicitly.&lt;/p&gt;

&lt;p&gt;And &lt;a href="https://github.com/ente-io/photos-app/pull/759"&gt;here&lt;/a&gt; you can see a real example of an inherited widget in use in the ente Photos app. No third party dependencies, and everything feels nice and solid. Happy 2023!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>tutorial</category>
      <category>mobile</category>
    </item>
    <item>
      <title>ente Auth</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Sat, 10 Dec 2022 09:22:38 +0000</pubDate>
      <link>https://forem.com/enteio/ente-auth-lhh</link>
      <guid>https://forem.com/enteio/ente-auth-lhh</guid>
      <description>&lt;p&gt;&lt;em&gt;Free and Open source E2EE Authenticator app by the makers of ente Photos&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ente Photos was born out of our own needs, as casual photographers who like to cherish memories.&lt;/p&gt;

&lt;p&gt;Over the last year, while developing our infrastructure, we had a hard time finding a place to preserve our two-factor secrets.&lt;/p&gt;

&lt;p&gt;Given that ente already had a platform for reliably storing and syncing data, encrypted end-to-end, we decided to leverage that to cure this itch.&lt;/p&gt;

&lt;p&gt;Introducing,&lt;/p&gt;

&lt;h3&gt;
  
  
  ente Auth
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tGqHiTvg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bun83t7hdfkxi25p24v4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tGqHiTvg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bun83t7hdfkxi25p24v4.png" alt="Screenshots of the Authenticator app" width="880" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ente Auth is an open-source authenticator app that will let you backup and view your 2FA secrets. You can find more information about the project here: &lt;a href="https://github.com/ente-io/auth#readme"&gt;github.com/ente-io/auth&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In an attempt to give back to the community who have helped us build all that we have, this service will be offered free of cost.&lt;/p&gt;

&lt;p&gt;If in the future we convert this to a paid service, existing users will be grandfathered in. So please sign up @ &lt;a href="https://dev.to/auth"&gt;ente.io/auth&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now back to building the best photos app :)&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Making our Figma public</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Wed, 07 Dec 2022 07:48:35 +0000</pubDate>
      <link>https://forem.com/enteio/making-our-figma-public-27ed</link>
      <guid>https://forem.com/enteio/making-our-figma-public-27ed</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Fear is the mind killer"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Like many of you, we find ourselves being held back by fear. By fear of judgement. By fear of being wrong, and that too in public. By the fear of not being good enough.&lt;/p&gt;

&lt;p&gt;As the &lt;em&gt;Litany Against Fear&lt;/em&gt; proclaims, we must not fear.&lt;/p&gt;

&lt;p&gt;Today, we face our fear, and go even further, fully embracing it: we are making our Figma files public.&lt;/p&gt;

&lt;p&gt;These are the Figma screens where we ideate, plan and execute how ente apps look and behave. There is nothing left out of it - these are in their full, forever-in-progress chaos.&lt;/p&gt;

&lt;p&gt;If you open it, you'll see our designers scurrying about hammering nails, creating components; and engineers ambling around with their tapes, measuring the designs as they translate them into code. Designs left unimplemented, designs yet to be picked; designs from an earlier era, designs for a future, it's all here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L6qAVGS9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/03v1s7l2dd8gxwcm59xk.png" alt="ente Visual Design" width="880" height="673"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unlike many other companies that published distilled versions of their "design systems", for us here there is no separate publish step – the link is to the actual, live, designs that we're using right now to build all our mobile, web and desktop apps. Any update we make, you'll see and can comment on; reminiscent of how GitHub works for source code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I will face my fear. I will permit it to pass over me and through me. And when it has gone past I will turn the inner eye to see its path."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We do not have an end goal in mind. The ente apps are already &lt;a href="https://github.com/ente-io"&gt;open source&lt;/a&gt;, and you'll find us active on our &lt;a href="https://ente.io/discord"&gt;Discord&lt;/a&gt; and other public channels like &lt;a href="https://mstdn.social/@ente"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://twitter.com/enteio"&gt;Twitter&lt;/a&gt;. So making ente's designs public is just another advance in this spirit of building out in the open and continuously living in the midst of customer feedback.&lt;/p&gt;

&lt;p&gt;There are downsides, yes, of such an approach. By getting early feedback, there are many things we will have to discard - not because they were bad ideas, but simply because of limited engineering bandwidth and other constraints. The people giving the feedback might not know of these details, and so there is a risk of them feeling disregarded. On the flipside, some customers might get too expectant of WIP designs that never see the light of the app, and thus we risk setting up false hopes too. So we're not sure if making this public is a good idea; let's see where this goes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Today, every digital product is a work in progress. And this has changed how we design. Our work never feels done because it isn't... It's the chaotic reality of modern product design and development.&lt;/p&gt;

&lt;p&gt;–– &lt;em&gt;Making progress in an in-progress world (from Figma's blog)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thank you for reading, and we hope you take a look. &lt;a href="https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design"&gt;Here is the link&lt;/a&gt;. And if there are suggestions you have on the designs, just press the C button to enter "Comment" mode (you might need a Figma account to be able to comment).&lt;/p&gt;

&lt;p&gt;See you in the Figma file!&lt;/p&gt;

</description>
      <category>design</category>
      <category>figma</category>
      <category>beginners</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Using Postgres as a task queue for rowdy Gophers</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Wed, 30 Nov 2022 06:31:49 +0000</pubDate>
      <link>https://forem.com/enteio/using-postgres-as-a-task-queue-for-rowdy-gophers-543l</link>
      <guid>https://forem.com/enteio/using-postgres-as-a-task-queue-for-rowdy-gophers-543l</guid>
      <description>&lt;p&gt;We love Postgres. It plays at the core (alongwith Go) of our conception of using "boring software" to make rock solid infrastructure.&lt;/p&gt;

&lt;p&gt;But if all one has a hammer, everything starts looking like a nail 🤖&lt;/p&gt;

&lt;p&gt;In this case, the nail that I wanted to hammer was keeping a queue of pending tasks that can then be picked up by a bunch of goroutines (or &lt;em&gt;Gophers&lt;/em&gt;, as Rob Pike endearingly refers to them in his talk on how &lt;a href="https://www.youtube.com/watch?v=oV9rvDllKEg" rel="noopener noreferrer"&gt;Concurrency is not Parallelism&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;But we don't just have one server, we have multiple machines, so native Go communication mechanisms like channels wouldn't suffice to prevent these Gophers from stepping on each others toes; they're not in the same address space and can't see each other.&lt;/p&gt;

&lt;p&gt;Plus, every once in a while the Docker containers that run our Go code will restart as we roll out updates. These Gophers would all sink alongwith the container, and we'd lose sight of what remains doing.&lt;/p&gt;

&lt;p&gt;Enough of these gruesome metaphors of dying Gophers though (So that's where the name "Gru" comes from!). We needed a persistent queue. And the hammer I had was Postgres.&lt;/p&gt;

&lt;p&gt;But turns out that SQL is not a great fit for keeping track of queues. Or at least, not in the general case. This is why there is a plethora of queuing solutions out there. From a simple Redis to a monstrous Kafka, a poison can be had of your choosing.&lt;/p&gt;

&lt;p&gt;But I like my hammer! More seriously though, a corollary to the ideology of using boring software is to also keep the number of moving parts to a minimum. And the queue I wanted to implement was not for an arbitrary general case, it was for a more specific case.&lt;/p&gt;

&lt;p&gt;So this is a story of what I had to do to meet that goal.&lt;/p&gt;




&lt;p&gt;Instead of coming up with an hypothetical example to tell the story, I'll continue with a simplified version of the original problem I'd needed to solve. Some of the details might be superfluous, but in lieu of the extra details we'll get a more concrete and realistic illustration of where Postgres as a queue is a reasonable choice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're just looking for the code to copy paste 😅, scroll down to the end to the part with &lt;code&gt;UPDATE...SKIP LOCKED&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, the problem statement.&lt;/p&gt;

&lt;p&gt;We replicate encrypted user data across three cloud providers. Why three? We'd started with two – 1 hot, 1 cold. But after seeing the latencies for the cold one, we realized that just the cold one would result in a long delay in case of disaster recovery, so then we figured we needed another hot one.&lt;/p&gt;

&lt;p&gt;So two hot buckets, and one cold one. And our client apps upload to the primary hot bucket.&lt;/p&gt;

&lt;p&gt;Replication is then done by goroutines running on our servers, and is conceptually quite simple - download the file from the primary hot bucket (Backblaze "b2"), and upload it to the other two buckets ("wasabi", and Scaleway "scw").&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are many more details, like using object locks to ensure files cannot be deleted even in case of credential leaks. But more on all that in a separate post coming soon!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These goroutines need to know which files are pending replication. And since the replication can take time for larger files, so they also need to ensure that another Gopher doesn't come along at the same time and starts replicating the same file.&lt;/p&gt;

&lt;p&gt;Also, it is possible that a Gopher might be able to upload a file to only one of the replicas. The buckets are in different clouds, and there can be transient failures in one of them. In such cases the Gopher should mark that it was uploaded in one of the places, and leave the second upload for some other Gopher to come back later and pick up, when the weather permits.&lt;/p&gt;

&lt;p&gt;Finally, the server might restart in the middle of the upload, and eventually such uncompleted uploads should get picked up again.&lt;/p&gt;

&lt;p&gt;Listing down the requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep track of which files need to be uploaded, and where.&lt;/li&gt;
&lt;li&gt;Lock a file that is currently being uploaded.&lt;/li&gt;
&lt;li&gt;Clean up stray locks on unexpected restarts.&lt;/li&gt;
&lt;li&gt;Do all of this scalably so that we can have many Gophers running in parallel.&lt;/li&gt;
&lt;li&gt;As much as possible, make it fast.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Quite a bit.&lt;/p&gt;

&lt;p&gt;No bother. Let's start simple, and create a SQL table that suffices for 1 &amp;amp; 2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;b2&lt;/span&gt;         &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;wasabi&lt;/span&gt;     &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scw&lt;/span&gt;        &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;lock&lt;/span&gt;       &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In S3 parlace, files are referred to as "objects", and are stored in bucket after being associated with unique keys. Thus, the table is named &lt;code&gt;object_copies&lt;/code&gt;, and the primary key is &lt;code&gt;object_key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then there are three fields, one for each of the replicas. We actually need just a boolean value to indicate whether or not the file has been yet added to that replica. But hey, let's splurge a bit and store the timestamp when we added them. Felt cute, might delete later.&lt;/p&gt;

&lt;p&gt;Finally, there is a field (&lt;code&gt;lock&lt;/code&gt;) to indicate whether the file is currently undergoing replication. This one needs to be a timestamp, a boolean won't suffice, because by having a timestamp we can go back and clean out stale entries that are too old (we'll see how in a bit).&lt;/p&gt;

&lt;p&gt;Given this table, a Gopher can find the next file that needs replication by using the following query. This'll select rows where one or more of the copies hasn't been added yet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The value of the field for the primary hot storage (in this case &lt;code&gt;b2&lt;/code&gt;) doesn't matter since it'll always be there - the code that gets the row into this table in the first place will ensure that.&lt;/p&gt;

&lt;p&gt;But it is not redundant. For the purpose of keeping this post short I'll not go further into it, but having it there allows us to swap primary / secondary replicas, add entirely new replicas with different cloud providers in the future, and even allows the possiblity of selective replication, since all the replicas are symmetrically reflected in the schema.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A Gopher is quite small and can't carry multiple files, and the previous query would give the poor soul all the burden in the world. So let's modify the query to only find one item by adding a &lt;code&gt;LIMIT&lt;/code&gt; clause.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we get an item, we also want to set the lock field to tell other Gophers to keep away from this one. Let's do that by wrapping our query in an update.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_utc_micro_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not quite correct though. We should also ignore rows that've already been locked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_utc_micro_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ah, but that's still not correct. &lt;/p&gt;

&lt;p&gt;Two Gophers might run the same query at the same time, grab the same row before the first one can notice that the other one has also updated the lock. You see, despite it's appearance to us as a nested query, for the the database these are effectively two sequential operations - a &lt;code&gt;SELECT&lt;/code&gt; followed by an &lt;code&gt;UPDATE&lt;/code&gt;. That is, roughly speaking, this is what Postgres sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEGIN TRANSACTION;

SELECT object_key FROM ...;

UPDATE object_copies SET lock = ...;

COMMIT TRANSACTION;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if two Gophers were to run this at the same time, we could end up with an interleaving like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT 1 -- Gopher 1
SELECT 2 -- Gopher 2 also selects the same row
UPDATE 1
UPDATE 2 -- And both update the same row
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we want here is a way to tell Postgres to "lock" the row it selects for the duration of the transaction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a different "lock" than our field. What I'm talking of here is a database row level lock that Postgres can place on a row to prevent other transactions from touching it. It is is different from our conceptual, application level lock taht we place using the &lt;code&gt;lock&lt;/code&gt; column. &lt;/p&gt;

&lt;p&gt;I guess I could rename the field to &lt;code&gt;gopher_lock&lt;/code&gt; to make it less confusing, but I doubt my colleagues will find my humor funny when they look back at this code 😶‍🌫️ so let's keep calling our field &lt;code&gt;lock&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SQL provides for such row-level locking with the &lt;code&gt;FOR UPDATE&lt;/code&gt; hint we can pass to our &lt;code&gt;SELECT&lt;/code&gt; statement. This will cause the rows that are selected to also be locked for the duration of the transaction, as if they'd been updated not just selected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_utc_micro_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Some of you might be wondering whether this transaction that I'm speaking of, if that is in the room with us? &lt;/p&gt;

&lt;p&gt;After all, we didn't write the &lt;code&gt;BEGIN&lt;/code&gt; and &lt;code&gt;END TRANSACTION&lt;/code&gt; anywhere?&lt;/p&gt;

&lt;p&gt;Indeed, it is there. Postgres wraps our &lt;code&gt;UPDATE ... (SELECT ...)&lt;/code&gt; in an implicitly generated transaction when we run that command (or any command / query for that matter).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the row level lock, the select + update will be "atomic", and is now functionally correct. &lt;/p&gt;

&lt;p&gt;But it has a more insidiuous problem now - &lt;strong&gt;Lock contention&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine we have 30 Gophers working in parallel. At any moment in time there will be multiple of them that'll be running this statement to find the next task. Given the deterministic nature of the queries, they'll all end up with wanting the same row, but only one of them will get it, and rest will all sit there idle, twiddling their thumbs. Gopher thumbs are quite small and not really comfortable to twiddle either.&lt;/p&gt;

&lt;p&gt;One might imagine (as I had) that this problem can be solved, if not in theory at least in practice, by having some form of randomness in the order in which rows are returned. So on a big enough table, each Gopher will end up getting a different row first instead of contending over the same row.&lt;/p&gt;

&lt;p&gt;Unfortunately, there isn't a magical &lt;code&gt;ORDER BY RANDOM&lt;/code&gt; in SQL.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is a seemingly similar &lt;code&gt;ORDER BY RANDOM()&lt;/code&gt;, but that is unusably inefficient - it always does a full table scan, generating a random number for each row, sorts the full table that way, and then returns it. Disk grindingly CPU meltingly inefficient.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That said, there are workarounds to achieve a random ordering. But they're just that, workarounds, I didn't find any of them satisfying, especially given that there is a simpler way.&lt;/p&gt;

&lt;p&gt;Now is the moment to introduce the hero of our story -- &lt;code&gt;SKIP LOCKED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SKIP LOCKED&lt;/code&gt; is a special hint we can give to Postgres to tell it to just move on if it finds a row that has been locked by someone else. This normally wouldn't make sense for database queries (since it will give an inconsistent view of the data), but it is exactly the thing we need if we're trying ot use the database as a queue. In fact, the queue use case is &lt;em&gt;the&lt;/em&gt; reason why this hint was introduced in the first place.&lt;/p&gt;

&lt;p&gt;Armed with this bit of trickery, let us chant our spell again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_utc_micro_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt; &lt;span class="n"&gt;LOCKED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beautiful. There is one small garnish we need to add - when we run this query, our Gopher would also need to know the &lt;code&gt;object_key&lt;/code&gt; and which of the two replicas needs syncing. For this, we can add a &lt;code&gt;RETURNING&lt;/code&gt; clause to the &lt;code&gt;UPDATE&lt;/code&gt; to return all relevant fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_utc_micro_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt; &lt;span class="n"&gt;LOCKED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;object_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wasabi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our query is now complete.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In case you're wondering where all of this magic is documented, stop (wondering). &lt;/p&gt;

&lt;p&gt;It is all there in the excellent &lt;a href="https://www.postgresql.org/docs/" rel="noopener noreferrer"&gt;PostgreSQL documentation&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We do need help from other sources to connect the various parts sometimes, e.g. this &lt;a href="https://www.enterprisedb.com/blog/what-skip-locked-postgresql-95" rel="noopener noreferrer"&gt;post about SKIP &amp;gt; LOCKED&lt;/a&gt; was very helpful to me. But the details are all there in the Postgres documentation itself, so RTFM.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;So are we done here?&lt;/p&gt;

&lt;p&gt;Let's look back at our original list of requirements:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Keep track of which files need to be uploaded, and where.&lt;/li&gt;
&lt;li&gt;Lock a file that is currently being uploaded.&lt;/li&gt;
&lt;li&gt;Clean up stray locks on unexpected restarts.&lt;/li&gt;
&lt;li&gt;Do all of this scalably so that we can have many Gophers running in parallel.&lt;/li&gt;
&lt;li&gt;As much as possible, make it fast.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Items 1 and 2 are done, and so is 4.&lt;/p&gt;

&lt;p&gt;This post is already too long, so I won't delve into details for #3, but I'll give a brief overview.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Clean up stray locks on unexpected restarts.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;One approach to handle this cleanup would be to obviate the need for a cleanup (duh). We can do that by explicitly starting a transaction in which our &lt;code&gt;UPDATE/SELECT&lt;/code&gt; statement runs, but keep the transaction running as we do the replication. This way, if the replication were to not complete because of some reason, the transaction would never be committed and the next Gopher that comes around will find the same row in its original state, and pick it up.&lt;/p&gt;

&lt;p&gt;In fact, this way, we wouldn't need the &lt;code&gt;lock&lt;/code&gt; field at all.&lt;/p&gt;

&lt;p&gt;That said, for now I've gone with an external cleanup. There is a periodic "cron" goroutine that clears out any entries where the value of the &lt;code&gt;lock&lt;/code&gt; field is older than a day. Seems to be working fine for now, but I can foresee myself revisiting this some day and switching to the cleaner(!) first approach.&lt;/p&gt;




&lt;p&gt;So let us focus on requirement 5 for the rest of the post.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;As much as possible, make it fast.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;As with anything to do with databases, there is a straightforward answer on how to achieve that – add an index 😬&lt;/p&gt;

&lt;p&gt;Hell, let's add two 🥶&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE INDEX ON object_copies (wasabi) WHERE wasabi IS NULL;
CREATE INDEX ON object_copies (scw) WHERE scw IS NULL;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not the only possibility - there are other indicies we could've used too. For example, we could've added a single combined indexed with the same &lt;code&gt;WHERE&lt;/code&gt; clause as our actual &lt;code&gt;SELECT&lt;/code&gt; query.&lt;/p&gt;

&lt;p&gt;But by adding separate indexes, we allow for the possibility of future new columns for new replicas being added to the same table, without invalidating the existing indexes. The newer columns will get their own indexes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Another practical reason is that we have reporting dashboards that show the number of unreplicated files in each replica, and having these separate indexes per replica make those reporting queries fast.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One thing to note here is the &lt;code&gt;WHERE&lt;/code&gt; clause in the &lt;code&gt;CREATE INDEX&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The idea behind that being that usually there are only going to be a small number of new files (relative to the total number of files) that are not replicated yet. So by creating an index on &lt;code&gt;WHERE wasabi is NULL&lt;/code&gt; / &lt;code&gt;WHERE scw is NULL&lt;/code&gt;, we don't waste Postgres' time (and disk) asking it to keep an index on rows that have already been replicated.&lt;/p&gt;

&lt;p&gt;So does this work? Let's ask Postgres!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EXPLAIN
UPDATE object_copies SET lock = now_utc_micro_seconds()
WHERE object_key = (
    SELECT object_key FROM object_copies
    WHERE (wasabi IS NULL OR scw IS NULL) AND lock IS NULL
    LIMIT 1 FOR UPDATE SKIP LOCKED)
RETURNING object_key, wasabi, scw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is our final query as before, just wrapped in an &lt;code&gt;EXPLAIN&lt;/code&gt;, requesting Postgres to tell us about what it's doing behind the scenes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;                                    &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="n"&gt;PLAN&lt;/span&gt;
&lt;span class="c1"&gt;---------------------------------------------------------------------------------------&lt;/span&gt;
 &lt;span class="k"&gt;Update&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
   &lt;span class="n"&gt;InitPlan&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="k"&gt;Limit&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
           &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;LockRows&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
                 &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Seq&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="n"&gt;object_copies_1&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
                       &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
   &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;object_copies_pkey&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
         &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the &lt;code&gt;Seq Scan&lt;/code&gt;, we can see that Postgres did not use our newly created indices. Hmm.&lt;/p&gt;

&lt;p&gt;At this point come to mind the two excellent tips provided by pgMustard in their clearly titled post - &lt;a href="https://www.pgmustard.com/blog/why-isnt-postgres-using-my-index" rel="noopener noreferrer"&gt;Why is Postgres not using my index?!&lt;/a&gt; (the exclamation mark is my own 🤓)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are two main reasons that Postgres will not use an index. Either &lt;strong&gt;it can’t use the index&lt;/strong&gt;, or &lt;strong&gt;it doesn’t think using the index will be faster&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Their post goes on two show how you can differentiate between the two reasons. &lt;/p&gt;

&lt;p&gt;Let's start by ruling out reason 1. This is easy - we tell Postgres to imagine a world in which sequential scans are very costly&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;enable_seqscan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then run our &lt;code&gt;EXPLAIN&lt;/code&gt; again&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;                                     &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="n"&gt;PLAN&lt;/span&gt;
&lt;span class="c1"&gt;--------------------------------------------------------------------------------------&lt;/span&gt;
 &lt;span class="k"&gt;Update&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
   &lt;span class="n"&gt;InitPlan&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="k"&gt;Limit&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
           &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;LockRows&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
                 &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Bitmap&lt;/span&gt; &lt;span class="n"&gt;Heap&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="n"&gt;object_copies_1&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
                       &lt;span class="k"&gt;Recheck&lt;/span&gt; &lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                       &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                       &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;BitmapOr&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
                             &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Bitmap&lt;/span&gt; &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;object_copies_wasabi_idx&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                   &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                             &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Bitmap&lt;/span&gt; &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;object_copies_scw_idx&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                   &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;object_copies_pkey&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt;  &lt;span class="p"&gt;(...)&lt;/span&gt;
         &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It used our index! We don't need to understand all the details of what the &lt;code&gt;QUERY PLAN&lt;/code&gt; is telling us, but we can already see it is using &lt;code&gt;Bitmap Index Scan&lt;/code&gt; and &lt;code&gt;Bitmap Heap Scan&lt;/code&gt; and other fancy stuff instead of a plain old sequential scan.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you need to understand more of what the query plan is trying to tell us, the &lt;a href="https://www.postgresql.org/docs/12/using-explain.html" rel="noopener noreferrer"&gt;Postgres docs for EXPLAIN&lt;/a&gt; are a great resource.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the reason Postgres wasn't using our indicies earlier was because &lt;strong&gt;it doesn’t think using the index will be faster&lt;/strong&gt;. Why could that be?&lt;/p&gt;

&lt;p&gt;There isn't any data in the table! Let us add some data. First, let us reset &lt;code&gt;enable_seqscan&lt;/code&gt;,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;enable_seqscan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then let us add some rows to the table,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;generate_series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and run the &lt;code&gt;EXPLAIN&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;Unfortunately, this time we'll find that it went back to using a sequential scan.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're not seeing the results that we're discussing, then try to run&lt;/p&gt;


&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;and re-run the &lt;code&gt;EXPLAIN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ANALYZE&lt;/code&gt; causes Postgres to refresh its statistics about what the table contains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe we need to add more data. What's a million rows anyways, a table for ants?&lt;/p&gt;

&lt;p&gt;But no, that's not the issue here. In addition to the amount of data, Postgres also considers the distribution of data. Currently, all our rows have all both &lt;code&gt;wasabi&lt;/code&gt; and &lt;code&gt;scw&lt;/code&gt; set to &lt;code&gt;NULL&lt;/code&gt;, so using the indicies we'd created wouldn't help in any way.&lt;/p&gt;

&lt;p&gt;Let us try to modify the data in the table to reflect the sort of scenario we'll face in production -- most rows have been replicated, but here and there are a few files that are left.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;wasabi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object_key&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;object_copies&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;scw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object_key&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let us run &lt;code&gt;EXPLAIN&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;Indeed, this time it uses the indices!&lt;/p&gt;

&lt;p&gt;So with that, our work here is done.&lt;/p&gt;




&lt;p&gt;Hopefully this was useful to you. And if not useful, at least you found my bumbling around to the final solution enjoyable.&lt;/p&gt;

&lt;p&gt;Till next time, happy hacking!&lt;/p&gt;

</description>
      <category>react</category>
      <category>discuss</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Custom S3 requests with AWS Go SDK</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Wed, 23 Nov 2022 05:28:20 +0000</pubDate>
      <link>https://forem.com/enteio/custom-s3-requests-with-aws-go-sdk-4ga4</link>
      <guid>https://forem.com/enteio/custom-s3-requests-with-aws-go-sdk-4ga4</guid>
      <description>&lt;p&gt;&lt;em&gt;Making requests to custom S3 endpoints using the AWS Go SDK&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Maybe there is an easier way to do this.&lt;/p&gt;

&lt;p&gt;Apologies for starting this post on an indecisive note, but I'm still not sure if this is indeed the "correct" way. It works though 🤷&lt;/p&gt;

&lt;p&gt;What I wanted was to make a request to a custom endpoint provided by an S3 compatible service. This endpoint looks and behaves very much like standard S3 APIs, but since it is not part of the suite that AWS provides the AWS SDKs don't have a way to directly use it.&lt;/p&gt;

&lt;p&gt;Of course, I could make an HTTP request on my own. I'd even be fine with parsing the XML (yuck!). But what I didn't want to deal with were signatures.&lt;/p&gt;

&lt;p&gt;So I thought to myself that there would be standard recipes to use the AWS SDK (I'm using the golang one) to make requests to such custom endpoints. But to my surprise, I didn't find any documentation or posts about doing this. So, yes, I wrote one 🙂&lt;/p&gt;

&lt;p&gt;The end result is quite simple - we tell the AWS Go SDK about our custom input and output payloads, and the HTTP path, and that's about it, it does all the heavy lifting (signing the requests, the HTTP request itself, and XML parsing) for us.&lt;/p&gt;

&lt;p&gt;You can scroll to the bottom if you just want the code.&lt;/p&gt;

&lt;p&gt;Otherwise, I'll walk through this in two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First we'll see how requests to standard endpoints are made under the hood.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then we'll use that knowledge to get the SDK to make our custom payloads and request go through the same code paths.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Enough background, let's get started. Let's use the Go SDK to fetch the versioning status of an S3 bucket.&lt;/p&gt;

&lt;p&gt;Why versioning? It is a simple GET request, conceptually and in code – we're just fetching a couple of attributes attached to a bucket. This way, we can focus on the mechanics of the request without getting lost in the details of the specific operation we're performing.&lt;/p&gt;

&lt;p&gt;Here is the code. Create a session. Use the session to create a client. Use the client to make the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/aws/aws-sdk-go/aws"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/aws/aws-sdk-go/aws/credentials"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/aws/aws-sdk-go/aws/session"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/aws/aws-sdk-go/service/s3"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Example credentials to connect to local MinIO. Don't hardcode your&lt;/span&gt;
    &lt;span class="c"&gt;// credentials if you're doing this for real!&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewStaticCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"minioadmin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"minioadmin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Region&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c"&gt;// Makes it easy to connect to localhost MinIO instances.&lt;/span&gt;
        &lt;span class="n"&gt;S3ForcePathStyle&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Endpoint&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:9000"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogDebugWithHTTPBody&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NewSession error: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;s3Client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"my-bucket"&lt;/span&gt;

    &lt;span class="c"&gt;// Create a bucket, ignoring any errors if it already exists.&lt;/span&gt;
    &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBucketInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c"&gt;// Everything above was just preparation, let's make the actual request.&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GetBucketVersioning error: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GetBucketVersioning output: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run this code, add it to a new go project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;s3-playground
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;s3-playground
&lt;span class="nv"&gt;$ &lt;/span&gt;go mod init example.org/s3-playground
&lt;span class="c"&gt;# Paste the above code into main.go&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;pbpaste &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; main.go
&lt;span class="nv"&gt;$ &lt;/span&gt;go get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a separate terminal, start a local MinIO instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 9000:9000 minio/minio server /data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back in our original terminal, run the program.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything went according to plan, you'll now see debug logs of the HTTP requests made by our program, and finally it will print the response for the bucket versioning request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;GetBucketVersioning&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Great. So now that we have our playground, let's dissect the request. The heart of the program is this bit of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is all quite straightforward.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an input payload (&lt;code&gt;GetBucketVersioning&lt;/code&gt;&lt;strong&gt;&lt;code&gt;Input&lt;/code&gt;&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Make an API call with that input (&lt;code&gt;GetBucketVersioning&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Get back the output (of type &lt;code&gt;GetBucketVersioning&lt;/code&gt;&lt;strong&gt;&lt;code&gt;Output&lt;/code&gt;&lt;/strong&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So if we were to make a custom API request, we already know that we'll need a &lt;code&gt;MyCustomOperation&lt;/code&gt;&lt;strong&gt;&lt;code&gt;Input&lt;/code&gt;&lt;/strong&gt; and &lt;code&gt;MyCustomOperation&lt;/code&gt;&lt;strong&gt;&lt;code&gt;Output&lt;/code&gt;&lt;/strong&gt;. But that's not enough, we'll also need to change the HTTP request path, possibly even the HTTP method.&lt;/p&gt;

&lt;p&gt;So we need to unravel one layer.&lt;/p&gt;

&lt;p&gt;If you were to click through to the source of &lt;code&gt;GetBucketVersioning&lt;/code&gt; in your editor, you'll see the following code (in &lt;a href="https://github.com/aws/aws-sdk-go/blob/main/service/s3/api.go"&gt;aws-sdk-go/service/s3/api.go&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetBucketVersioning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is creating a &lt;code&gt;Request&lt;/code&gt; and an &lt;code&gt;Output&lt;/code&gt;. &lt;code&gt;Send&lt;/code&gt;ing the request. And returning the &lt;code&gt;Output&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's keep digging. Looking at the source of &lt;code&gt;GetBucketVersioningRequest&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetBucketVersioningRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningOutput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;opGetBucketVersioning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;HTTPMethod&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;HTTPPath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"/{Bucket}?versioning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningInput&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningOutput&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice, we can see where HTTP request path and HTTP method are being set. Let us also look into the source of &lt;code&gt;newRequest&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Run custom request initialization if present&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;initRequest&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;initRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is essentially doing &lt;code&gt;c.NewRequest&lt;/code&gt; (the rest of the code is for allowing us to intercept the request before it is used).&lt;/p&gt;

&lt;p&gt;So now we have all the actors on the stage. Let us inline the code that we saw above in the AWS Go SDK source into our own program. We will replace its original heart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with this inlined / rearranged version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// this'll need to import "github.com/aws/aws-sdk-go/aws/request"&lt;/span&gt;
&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"GetBucketVersioning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HTTPMethod&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HTTPPath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"/{Bucket}?versioning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBucketVersioningOutput&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is that it? The code is simple enough, and we can see the input payload, the request itself, and the parsed output. If we substitute each of these with our custom versions, will it just work?&lt;/p&gt;

&lt;p&gt;There's only one way to find out 😎&lt;/p&gt;




&lt;p&gt;For this second part of this post, I tried finding an example MinIO-only API for pedagogical purposes, but couldn't find one off hand. So let's continue by using an example related to the actual custom endpoint that I'd needed to use.&lt;/p&gt;

&lt;p&gt;We use Wasabi as one of the replicas for storing encrypted user data. We'll put out an article soon with details about ente's replication, but for our purposes here it suffices to mention that we use the Wasabi's "Compliance" feature to ensure that user data cannot be deleted even if some attacker were to get hold of Wasabi API keys.&lt;/p&gt;

&lt;p&gt;To fit this into our replication strategy, we need to make an API call from our servers to tell Wasabi to "unlock" the file and remove it from the compliance protection when the user herself deletes it, so that it can then be scheduled for deletion after the compliance period is over.&lt;/p&gt;

&lt;p&gt;To keep the post simple, let us consider a related but simpler custom &lt;a href="https://wasabi.com/wp-content/themes/wasabi/docs/API_Guide/index.html#t=topics%2FCompliance.htm&amp;amp;rhsyns=%20"&gt;Wasabi API&lt;/a&gt;: getting the current compliance settings for a bucket.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The compliance settings for a bucket can be retrieved by getting the bucket with the "?compliance" query string. For example:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET http://s3.wasabisys.com/my-buck?complianceHTTP/1.1
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Response body:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;BucketComplianceConfiguration xml ns="http://s3.amazonaws.com/doc/2006-03-01/"&amp;gt;
   &amp;lt;Status&amp;gt;enabled&amp;lt;/Status&amp;gt;
   &amp;lt;LockTime&amp;gt;2016-11-07T15:08:05Z&amp;lt;/LockTime&amp;gt;
   &amp;lt;IsLocked&amp;gt;false&amp;lt;/IsLocked&amp;gt;
   &amp;lt;RetentionDays&amp;gt;0&amp;lt;/RetentionDays&amp;gt;
   &amp;lt;ConditionalHold&amp;gt;false&amp;lt;/ConditionalHold&amp;gt;
   &amp;lt;DeleteAfterRetention&amp;gt;false&amp;lt;/DeleteAfterRetention&amp;gt;
&amp;lt;/BucketComplianceConfiguration&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;As you can see, it looks and behaves just like other S3 REST APIs. Except that the AWS Go SDK does not have a pre-existing method to perform this operation.&lt;/p&gt;

&lt;p&gt;So how do we call this API using the AWS Go SDK?&lt;/p&gt;

&lt;p&gt;Let us modify code that we ended up with in first section, but retrofit the input / request / output objects to match the documentation of this custom API.&lt;/p&gt;

&lt;p&gt;Let's start with definition of &lt;code&gt;GetBucketVersioningInput&lt;/code&gt; from the AWS Go SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;GetBucketVersioningInput&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="s"&gt;`locationName:"GetBucketVersioningRequest" type:"structure"`&lt;/span&gt;

    &lt;span class="n"&gt;Bucket&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`location:"uri" locationName:"Bucket" type:"string" required:"true"`&lt;/span&gt;

    &lt;span class="n"&gt;ExpectedBucketOwner&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`location:"header" locationName:"x-amz-expected-bucket-owner" type:"string"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmm. We don't seem to need the &lt;code&gt;ExpectedBucketOwner&lt;/code&gt; field, so let's remove that. We do need the &lt;code&gt;Bucket&lt;/code&gt; field, and it is passed in the as &lt;code&gt;location:"uri"&lt;/code&gt;, so let's keep the rest as it is, and arrive at our retrofitted &lt;code&gt;GetBucketComplianceInput&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;GetBucketComplianceInput&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="s"&gt;`locationName:"GetBucketComplianceRequest" type:"structure"`&lt;/span&gt;

    &lt;span class="n"&gt;Bucket&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`location:"uri" locationName:"Bucket" type:"string" required:"true"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let us repeat the process for the Output. Here's the original:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;GetBucketVersioningOutput&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="s"&gt;`type:"structure"`&lt;/span&gt;

    &lt;span class="n"&gt;MFADelete&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`locationName:"MfaDelete" type:"string" enum:"MFADeleteStatus"`&lt;/span&gt;

    &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`type:"string" enum:"BucketVersioningStatus"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here it is, modified to match the documentation of the custom API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;GetBucketComplianceOutput&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="s"&gt;`type:"structure"`&lt;/span&gt;

    &lt;span class="n"&gt;Status&lt;/span&gt;               &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`type:"string"`&lt;/span&gt;
    &lt;span class="n"&gt;LockTime&lt;/span&gt;             &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`type:"string"`&lt;/span&gt;
    &lt;span class="n"&gt;RetentionDays&lt;/span&gt;        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;  &lt;span class="s"&gt;`type:"integer"`&lt;/span&gt;
    &lt;span class="n"&gt;ConditionalHold&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;   &lt;span class="s"&gt;`type:"boolean"`&lt;/span&gt;
    &lt;span class="n"&gt;DeleteAfterRetention&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;   &lt;span class="s"&gt;`type:"boolean"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let us change the middle part – the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"GetBucketCompliance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HTTPMethod&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HTTPPath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"/{Bucket}?compliance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding the new definitions of &lt;code&gt;GetBucketComplianceInput&lt;/code&gt; and &lt;code&gt;GetBucketComplianceOutput&lt;/code&gt; and making the other changes, we'll end up with this final version of the heart of our program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;GetBucketComplianceInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;"GetBucketCompliance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HTTPMethod&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HTTPPath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"/{Bucket}?compliance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;GetBucketComplianceOutput&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GetBucketCompliance error: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;GetBucketCompliance status: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will this work 😅? Now we'll find out.&lt;/p&gt;

&lt;p&gt;You'll have to take my word (or get a Wasabi account), but if I take the program with these changes, add proper credentials, and run it, it does indeed work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go run main.go
...
2022/11/22 21:29:38 DEBUG: Response s3/GetBucketCompliance Details:
&lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; RESPONSE &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nt"&gt;--------------------------------------&lt;/span&gt;
HTTP/1.1 200 OK
Content-Length: 136
Content-Type: text/xml
Date: Tue, 22 Nov 2022 15:59:38 GMT
Server: WasabiS3/7.9.1306-2022-11-09-489242991d &lt;span class="o"&gt;(&lt;/span&gt;head3&lt;span class="o"&gt;)&lt;/span&gt;
X-Amz-Id-2: ...
X-Amz-Request-Id: ...


&lt;span class="nt"&gt;-----------------------------------------------------&lt;/span&gt;
2022/11/22 21:29:38 &amp;lt;BucketComplianceConfiguration &lt;span class="nv"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://s3.amazonaws.com/doc/2006-03-01/"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&amp;lt;Status&amp;gt;disabled&amp;lt;/Status&amp;gt;&amp;lt;/BucketComplianceConfiguration&amp;gt;

GetBucketCompliance status: disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sweet!&lt;/p&gt;

&lt;p&gt;Also, hats off to the AWS gophers for a well designed SDK - we needed to just modify the payloads &amp;amp; change the HTTP path, everything else just worked.&lt;/p&gt;




&lt;p&gt;In the end, the solution is simple and is just the obvious set of changes one can expect, but when I was doing this I hadn't been sure until the moment I actually made the final request if it'll work or not. So I hope this post might be useful to someone who finds themselves on the same road - Carry on, it'll work!&lt;/p&gt;

&lt;p&gt;Till next time, happy coding 🧑‍💻&lt;/p&gt;

</description>
      <category>go</category>
      <category>aws</category>
      <category>tutorial</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Command to publish an app to TestFlight</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Thu, 22 Sep 2022 11:13:56 +0000</pubDate>
      <link>https://forem.com/enteio/command-to-publish-an-app-to-testflight-3o02</link>
      <guid>https://forem.com/enteio/command-to-publish-an-app-to-testflight-3o02</guid>
      <description>&lt;p&gt;At ente, we are building an &lt;a href="https://github.com/ente-io/frame" rel="noopener noreferrer"&gt;open-source&lt;/a&gt; iOS app for backing up and sharing your photos and videos.&lt;/p&gt;

&lt;p&gt;In the beginning, we used XCode to create an Archive and then painfully clicked 20 buttons to upload our build to TestFlight.&lt;/p&gt;

&lt;p&gt;We then discovered an easy way to do the same from the command line with these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure &lt;a href="https://apps.apple.com/us/app/transporter/id1450874784" rel="noopener noreferrer"&gt;Transporter&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;{command-to-build} &amp;amp;&amp;amp; open -a "Transporter" {path-to-generated-ipa}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;That's it!&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;In our case, since we are using &lt;a href="https://flutter.dev" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt;, the single line command to build and publish to TestFlight is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter build ipa &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; open &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"Transporter"&lt;/span&gt; build/ios/ipa/&lt;span class="k"&gt;*&lt;/span&gt;.ipa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Hope this will save you a few clicks! If you'd like to know more of our adventures with Flutter and AppStore, follow us on &lt;a href="https://twitter.com/enteio" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, or come hang out on our lovely &lt;a href="https://ente.io/discord" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>ios</category>
    </item>
    <item>
      <title>The minimum TypeScript you need for React</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Fri, 19 Aug 2022 16:09:06 +0000</pubDate>
      <link>https://forem.com/enteio/the-minimum-typescript-you-need-for-react-2hj0</link>
      <guid>https://forem.com/enteio/the-minimum-typescript-you-need-for-react-2hj0</guid>
      <description>&lt;p&gt;TypeScript is awesome. It allows one to remain in the fun wild-west of JavaScript, while being safe in the blanket of a state of the art type system. Plus, it a great way to adopt strong type checking gradually without having to rewrite the entire codebase, or get a Ph.D in type theory (I'm looking at you, Haskell 🦥).&lt;/p&gt;

&lt;p&gt;But TypeScript is also has a learning curve. For some of us, it's a steep one. Everything is well documented, but there is a lot of the said documentation 😅. There are also cheetsheets, say for using TypeScript with React, but they assume a basic understanding of TypeScript. So what is missing (or at least I haven't found) are quick recipes for the most common use cases we run into when trying to introduce TypeScript into an existing React app. Here goes.&lt;/p&gt;

&lt;h4&gt;
  
  
  The minimum TypeScript you need to know to type your way around React
&lt;/h4&gt;

&lt;p&gt;Let us start with a simple React component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Hello.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add types to this component, we need to add a type annotation to &lt;code&gt;Hello&lt;/code&gt;. This is what React calls as a "Function Component", or "FC" for short. React is a bro, and also provides us with its (TypeScript) type — &lt;code&gt;React.FC&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This (and the other types we'll add below) are part of the &lt;code&gt;@types/react&lt;/code&gt; and &lt;code&gt;@types/react-dom&lt;/code&gt; packages, so you'll need to have them installed.&lt;/p&gt;

&lt;p&gt;Moving on. A hello is nice, but saying hello while addressing a person by their name is nicer. Let's teach our &lt;code&gt;Hello&lt;/code&gt; component to accept a &lt;code&gt;name&lt;/code&gt; prop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh noes, the dreaded red squiggly appears.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dXPrHp9Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sm8z05swn3z2gqrit4p5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dXPrHp9Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sm8z05swn3z2gqrit4p5.png" alt="Property 'name' does not exist on type '{}'" width="880" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a good time to bring up an important point. TypeScript does its thing at compile-time (when we write the code). So even though it is complaining here, when we actually run the code ("runtime"), it will work as normal. In that sense, this is more of a warning than an error.&lt;/p&gt;

&lt;p&gt;So if we ignore TypeScript errors, things might or might not work at runtime. But if we don't ignore them, we can be sure that things will work at runtime (the things that TypeScript can check for, that is).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Back to the squiggly. What &lt;code&gt;tsc&lt;/code&gt; (&lt;strong&gt;T&lt;/strong&gt;ype*&lt;em&gt;S&lt;/em&gt;&lt;em&gt;cript **C&lt;/em&gt;*ompiler) is telling us is that a plain &lt;code&gt;React.FC&lt;/code&gt; does not take any props, but here we're trying to read a &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So we need to tell tsc that this is not a plain &lt;code&gt;React.FC&lt;/code&gt;, it is a enhanced version that also takes a &lt;code&gt;name&lt;/code&gt; prop, and that this &lt;code&gt;name&lt;/code&gt; is a string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This satisfies tsc. But it is not very readable, and it'll keep getting gookier the more props we add. So let us define a type that describes the props that our component takes, and then use &lt;em&gt;that&lt;/em&gt; type to enhance &lt;code&gt;React.FC&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HelloProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HelloProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect. &lt;/p&gt;

&lt;p&gt;Or not quite yet. What good is a web developer who greets a user without a CTA in mind? 😛&lt;/p&gt;

&lt;p&gt;But our Hello component is perfect as it is – like a textbook React component, it does one thing, and does one thing well, and is a great building block. Adding new functionality to this would spoil that perfection.&lt;/p&gt;

&lt;p&gt;So let us pass the CTA as a child. This way, we can reuse the &lt;code&gt;Hello&lt;/code&gt; component in different places to greet the user the same way yet call on them to do different things.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HelloProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HelloProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh noes, the red squiggly reappears! This time it is complaining that&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Property 'children' does not exist on type 'HelloProps'.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fair enough. To fix this, we can add &lt;code&gt;children&lt;/code&gt; to &lt;code&gt;HelloProps&lt;/code&gt;, though for doing that we'll need to figure out what is the type of &lt;code&gt;children&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But wait a sec. Taking children props looks like a common enough, bread and butter need for React components. Isn't there a standard type for such components?&lt;/p&gt;

&lt;p&gt;Why, indeed there is. It is called &lt;code&gt;PropsWithChildren&lt;/code&gt; (yay for nice descriptive names!).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HelloProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HelloProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Brilliant. &lt;/p&gt;

&lt;p&gt;We can't wait to use this to &lt;del&gt;yell at&lt;/del&gt; greet our users, and immediately start using it. It works great for a while, but then we run into a special page. Turns out, we do indeed need to yell at the user on the pricing page, to make sure they know that we really &lt;em&gt;really&lt;/em&gt; like them.&lt;/p&gt;

&lt;p&gt;Helpfully, our designer has written some nice CSS to style the yell appropriately, we just need to add that class to our div.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HelloProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HelloProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`{className ?? ''}`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guess what, the red squiggly strikes again!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Property 'className' does not exist on type 'PropsWithChildren'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We now know what this means, and how to fix it – we need to add &lt;code&gt;className&lt;/code&gt; to our props. But hold on, this too seems like a very standard, bread and butter type(!) thing for React components. Isn't there a standard type to say that we'd like &lt;code&gt;className&lt;/code&gt;, or &lt;code&gt;style&lt;/code&gt;, or &lt;code&gt;id&lt;/code&gt; or any one of the other such props that are accepted by HTML div elements?&lt;/p&gt;

&lt;p&gt;Glad you asked, because there is. It is called &lt;code&gt;HTMLAttributes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But it doesn't jive on its own – we also need to tell it which HTML element it is whose HTML attributes we're talking about. In our case, we're talking of a standard HTML div, so we will use &lt;code&gt;HTMLAttributes&amp;lt;HTMLDivElement&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HTMLAttributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HelloProps&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;HTMLAttributes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLDivElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HelloProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, really. Armed with these basic types, you'll know how to type your way around React. &lt;/p&gt;

&lt;p&gt;You'll hit more advanced cases eventually – say that instead of a vanilla div, you wish to create something that behaves like the HTML link (&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; - the anchor tag). But you won't remain befuddled for long on that, for now you know that you just need to find what's the &lt;code&gt;HTMLDivElement&lt;/code&gt; equivalent for a anchor tag and use that instead. And these types are quite nicely named, so that type is usually something whose name you can guess (for example, for an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag, it is &lt;code&gt;HTMLAttributes&amp;lt;HTMLAnchorElement&amp;gt;&lt;/code&gt;).&lt;/p&gt;




&lt;p&gt;Hope this was helpful! If you'd like to know more of our adventures with TypeScript, React, and other things of the ilk, do follow us on &lt;a href="https://twitter.com/enteio"&gt;Twitter&lt;/a&gt;, or come hang out on our lovely &lt;a href="https://ente.io/discord"&gt;Discord&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Tell next time, happy typ(e)ing! 🤓&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>gatsby</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Gatsby blog images in 2022</title>
      <dc:creator>ente</dc:creator>
      <pubDate>Thu, 28 Jul 2022 05:28:04 +0000</pubDate>
      <link>https://forem.com/enteio/gatsby-blog-images-in-2022-4o8i</link>
      <guid>https://forem.com/enteio/gatsby-blog-images-in-2022-4o8i</guid>
      <description>&lt;p&gt;We recently cleaned the image related code in our Gatsby blog, and this is a tldr of what we learned.&lt;/p&gt;

&lt;p&gt;Gatsby provides three options to handle images:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assets from filesystem (&lt;code&gt;import myImage from './myImage.png'&lt;/code&gt;) &lt;/li&gt;
&lt;li&gt;Gatsby image plugin (&lt;code&gt;&amp;lt;StaticImage ...&amp;gt;&lt;/code&gt; and it's dynamic variant) &lt;/li&gt;
&lt;li&gt;Static folder (&lt;code&gt;&amp;lt;img ...&amp;gt;&lt;/code&gt;) &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://www.gatsbyjs.com/docs/how-to/images-and-media/"&gt;https://www.gatsbyjs.com/docs/how-to/images-and-media/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For blog posts written in MDX, none of these are particularly convenient though. For that, Gatsby provides an official plugin&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gatsbyjs.com/plugins/gatsby-plugin-image/"&gt;https://www.gatsbyjs.com/plugins/gatsby-plugin-image/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This works quite seamlessly. We keep the image in the same folder as the &lt;code&gt;.mdx&lt;/code&gt; and can then use the standard Markdown &lt;code&gt;![alt text](./foo.png)&lt;/code&gt; syntax to include them in our posts.&lt;/p&gt;

&lt;p&gt;Behind the scenes, it'll use the same method as Gatsby image plugin (&lt;code&gt;#2&lt;/code&gt;, &lt;code&gt;&amp;lt;StaticImage...&amp;gt;&lt;/code&gt; above) and support responsive variants using srcsets + blurred placeholders + async loading etc. More details are about this are in&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gatsbyjs.com/docs/working-with-images-in-markdown/"&gt;https://www.gatsbyjs.com/docs/working-with-images-in-markdown/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The issue though is that there is only a global maxWidth option, and no way to specify it per image. Thankfully, there is a community plugin that allows us to do that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gatsbyjs.com/plugins/@bonobolabs/gatsby-remark-images-custom-widths/"&gt;https://www.gatsbyjs.com/plugins/@bonobolabs/gatsby-remark-images-custom-widths/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is intended as a drop-in replacement for &lt;code&gt;gatsby-remark-images&lt;/code&gt;, and supports most of the same options. To configure this, one needs to add the following to &lt;code&gt;gatsby-config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gatsby-plugin-mdx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;gatsbyRemarkPlugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@bonobolabs/gatsby-remark-images-custom-widths&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transparent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;withWebp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;linkImagesToOriginal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the &lt;code&gt;options&lt;/code&gt; are optional, the above are for illustration only. For example, we also needed to disable &lt;code&gt;linkImagesToOriginal&lt;/code&gt; because otherwise all the blog images would get wrapped in anchor tags, causing the mouse cursor to change to a hand pointer (detracting from the reading experience).&lt;/p&gt;

&lt;p&gt;Once it is configured, then using the plugin is simple. Just add an &lt;code&gt;img&lt;/code&gt; tag to your MDX, and specify a width:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Some example &lt;span class="gs"&gt;**markdown**&lt;/span&gt; content.

&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"cat.png"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"310px"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Cat!"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

More markdown content. Type away!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rest of the image processing pipeline will work the same way. We will get a blurred variant that is shown while the actual image is being downloaded, and the image being downloaded will be the size appropriate to the user's screen.&lt;/p&gt;

&lt;p&gt;For our blog, the convention we follow for now is that when adding images to MDX, we specify the 1x width but save the image at the 3x size. This way, Gatsby has high quality data to create large variants for users whose screens support 3x resolutions, but for other users it'll generate and fetch smaller images depending on their screen resolution and size.&lt;/p&gt;

&lt;p&gt;For example, if we have a 240 px wide image (the 1x size), then we'll store a 720 px width image (the 3x size), and in the MDX we'll specify &lt;code&gt;width="240"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We wouldn't let you come this far, talk of images, and not show you a cat pic!&lt;/p&gt;

&lt;p&gt;So, here, meet Honey&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q48sUtu0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7s1edoby43z7qm65jrl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q48sUtu0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7s1edoby43z7qm65jrl.jpg" alt="The cat named Honey" width="880" height="678"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;On issue that still bugs us is lack of SVG support. For this we have to fallback to either option &lt;code&gt;#1&lt;/code&gt; (Assets from filesystem) or option &lt;code&gt;#2&lt;/code&gt; (Static folder). Maybe one of you would be kind enough to let us know (&lt;a href="https://twitter.com/enteio"&gt;Twitter&lt;/a&gt;, &lt;a href="https://ente.io/discord"&gt;Discord&lt;/a&gt;) if there is a simpler way!&lt;/p&gt;

&lt;p&gt;Till next time, happy blogging 👩‍💻&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>writing</category>
      <category>webdev</category>
      <category>markdown</category>
    </item>
  </channel>
</rss>
