<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Iury's Thoughtbox Blog]]></title><description><![CDATA[Sharing ideas]]></description><link>https://iurysouza.dev</link><generator>GatsbyJS</generator><lastBuildDate>Tue, 09 Jun 2026 17:09:42 GMT</lastBuildDate><item><title><![CDATA[From GitHub Actions to Cloud Build]]></title><description><![CDATA[Back in 2024, I wanted a project where I could try building an agent-like app without getting buried in infrastructure. I ended up building…]]></description><link>https://iurysouza.dev/from-github-actions-to-cloud-build/</link><guid isPermaLink="false">https://iurysouza.dev/from-github-actions-to-cloud-build/</guid><pubDate>Wed, 03 Jun 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Back in 2024, I wanted a project where I could try building an agent-like app without getting buried in infrastructure. I ended up building the &lt;a href=&quot;../prototyping-an-ai-app-part-3/&quot;&gt;AI Audio Guide app&lt;/a&gt;: point the camera at a landmark, and get back a short explanation with narrated audio.&lt;/p&gt;
&lt;p&gt;In 2025 I also wanted a dedicated backend where I could explore RAG, image analysis, different models, tool calls, retrieval, and audio generation without stuffing all that product logic into the mobile client.&lt;/p&gt;
&lt;p&gt;The first version worked. It had a mobile client, a backend API, and enough moving pieces that deployment stopped being a side quest and became part of the product.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/4cecbdc95c16ed29be1455c36e221484/cloud-build-migration-mobile-app.webp&quot; alt=&quot;Audio guide mobile app showing generated narration for a landmark&quot; title=&quot;The mobile app showing a generated audio guide with playback controls. This is the product path the backend has to support.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cloud.google.com/run&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Cloud Run&lt;/a&gt; felt like the right place for that backend. That view got stronger after I had the chance to join an &lt;a href=&quot;https://www.linkedin.com/posts/iurysouza_wrapping-up-with-gemma-dev-day-and-a-solid-activity-7305864907982336003-LS1S&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;AI Paris event&lt;/a&gt; and talk with people from Google about Cloud Run. I did more research afterwards, especially around agents, and the product started to make sense: a real container, HTTPS, IAM, logs, Secret Manager integration, and request-based scaling without running a platform team for an experiment.&lt;/p&gt;
&lt;p&gt;I wanted containers, but I did not want to run servers or manage a Kubernetes cluster just to ship an experiment. I also did not want to squeeze the app into serverless functions. This was a backend with a few moving parts, model calls, retrieval, and audio generation. Cloud Run let the container stay the unit while Google Cloud handled the serving layer.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8468038fcd663dd4273b231851e83a01/c1817/cloud-build-migration-cloud-run-demo.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAcFCAn/xAAWAQEBAQAAAAAAAAAAAAAAAAAGAgX/2gAMAwEAAhADEAAAAVHLZlM4GlvIJE0q/8QAGRAAAwEBAQAAAAAAAAAAAAAABAUGAwcU/9oACAEBAAEFAt7JkoEd3xikZP0iWGEWlFtjbvXT2c5SKXE9/8QAIBEAAQMEAwEBAAAAAAAAAAAAAgEDBAUREzEAEhQhFf/aAAgBAwEBPwGouv1AqfVZjiuDGOULMYCVuMqSOjS+qIFos3HjE2PW2/hdHI31Jw+foqqrijsA3ewB10KfLaK2tXW2k+c//8QAIxEAAgIABQQDAAAAAAAAAAAAAgQBAwUREhQiAAYTMSFBQ//aAAgBAgEBPwHt3AMM7aJ1LCqfCy+ChOOCIhaUoCZU7awczQ1jfZWwSO3JoLCFibNI9bQ/3aYsunlYeueRFyn7HP37yiZ9z89f/8QAKBAAAgICAQIFBAMAAAAAAAAAAQIDBAUREiEiAAYTFSMUMTIzQVHR/9oACAEBAAY/AnfKYm/JVl1Wqyih7XHFO/1CCGOW5Nu+EirxTCdE4vyJQjaxrKmdsVb9n22hbxeOmkSu1uaTLXcbP8uylpvRqTM0vPie1f2g6Zc6li3dezYmVzRqbirzPzirM0RgSQ1ttD6oj+QIHPU+DYydy5fnOvlt27M79++XdJKT/n8a8YZObFafl2VavLu9BR5gycoERbZUCTuGj0JbX5Ns3cpQr3rTZC1GZp15PwjEQRR10FH9ADqSfuSfH//EABgQAQEBAQEAAAAAAAAAAAAAAAERAEFR/9oACAEBAAE/IWyMsBTtDBBzWPN6AAABq6LwSYQ5TlrOJWRdV4dfiylIQQoICDBsGHEQlcvz2+Zbfj1pPSQVUEt17//aAAwDAQACAAMAAAAQOC//xAAXEQEBAQEAAAAAAAAAAAAAAAABEQAx/9oACAEDAQE/EDgy82hTo4xbplwfUktDDwJgqEAB/8QAGBEBAQEBAQAAAAAAAAAAAAAAARFBACH/2gAIAQIBAT8QHwxAlalRF2N3Y9BKCEG2FexRL3//xAAWEAEBAQAAAAAAAAAAAAAAAAABABH/2gAIAQEAAT8QFVEhfzjXCMvCfZaKzoPFJBHnpBuGKSx5RiJJnsIkkMCagtMhfnJARhqYFH5EP2KtRJEXM//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/8468038fcd663dd4273b231851e83a01/577d7/cloud-build-migration-cloud-run-demo.webp 300w,
/static/8468038fcd663dd4273b231851e83a01/93ccb/cloud-build-migration-cloud-run-demo.webp 600w,
/static/8468038fcd663dd4273b231851e83a01/1aa16/cloud-build-migration-cloud-run-demo.webp 1200w,
/static/8468038fcd663dd4273b231851e83a01/daa23/cloud-build-migration-cloud-run-demo.webp 1800w,
/static/8468038fcd663dd4273b231851e83a01/b1e4f/cloud-build-migration-cloud-run-demo.webp 2400w,
/static/8468038fcd663dd4273b231851e83a01/e18a5/cloud-build-migration-cloud-run-demo.webp 4032w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/8468038fcd663dd4273b231851e83a01/622f1/cloud-build-migration-cloud-run-demo.jpg 300w,
/static/8468038fcd663dd4273b231851e83a01/58041/cloud-build-migration-cloud-run-demo.jpg 600w,
/static/8468038fcd663dd4273b231851e83a01/0516a/cloud-build-migration-cloud-run-demo.jpg 1200w,
/static/8468038fcd663dd4273b231851e83a01/3bb1d/cloud-build-migration-cloud-run-demo.jpg 1800w,
/static/8468038fcd663dd4273b231851e83a01/41eac/cloud-build-migration-cloud-run-demo.jpg 2400w,
/static/8468038fcd663dd4273b231851e83a01/c1817/cloud-build-migration-cloud-run-demo.jpg 4032w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/8468038fcd663dd4273b231851e83a01/0516a/cloud-build-migration-cloud-run-demo.jpg&quot;
            alt=&quot;Chatting about Cloud Run with its founder: Steren Giannini&quot;
            title=&quot;Chatting about cloud run with its founder: Steren Giannini&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Chatting about cloud run with its founder: Steren Giannini&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;mobile client
  -&gt; Cloud Run container
  -&gt; Google Cloud APIs + RAG storage&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once I had a containerized app, deployment automation became the next problem. I was iterating fast, so I reached for the closest tool: GitHub Actions. The code was already on GitHub, I knew the workflow model, and wiring a release event to a build script was easy. It was a good first solution.&lt;/p&gt;
&lt;p&gt;I also wanted the boring parts scripted. I was already familiar with the &lt;a href=&quot;https://12factor.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Twelve-Factor App&lt;/a&gt; ideas around config, build/release/run separation, and dev/prod parity. So I put most operations behind commands: start the app, run the vector store, build the container, push the image, bump the version. I wanted repeatable commands, not a checklist I had to reconstruct every time.&lt;/p&gt;
&lt;p&gt;It worked, but there were maybe too many moving parts and components spread across different services.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part of the system&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source code&lt;/td&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release trigger&lt;/td&gt;
&lt;td&gt;GitHub release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release runner&lt;/td&gt;
&lt;td&gt;GitHub Actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container images&lt;/td&gt;
&lt;td&gt;Artifact Registry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Cloud Run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IAM and logs&lt;/td&gt;
&lt;td&gt;Google Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets&lt;/td&gt;
&lt;td&gt;GitHub Secrets decoded into &lt;code class=&quot;language-text&quot;&gt;.secrets/&lt;/code&gt; during release&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Secrets were not committed to the repository. The shortcut was to have &lt;a href=&quot;https://github.com/features/actions&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GitHub Actions&lt;/a&gt; decode them during release, write them into &lt;code class=&quot;language-text&quot;&gt;.secrets/&lt;/code&gt;, and make those files available to the container.&lt;/p&gt;
&lt;p&gt;That was okay for moving fast. But I did not want the build path to keep acting like a secret delivery system. Secrets should be runtime concerns. Images should be deployable artifacts. If rotating a key makes me think about image rebuilds, the boundary is wrong.&lt;/p&gt;
&lt;p&gt;There was also the runner question. GitHub Actions works for my use case, but its runner queue has &lt;a href=&quot;https://damrnelson.github.io/github-historical-uptime/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;been stretched pretty thin lately&lt;/a&gt;. Since this service already ran on Google Cloud, I would prefer not tying production deploys to an external runner queue if Cloud Build could handle that part natively.&lt;/p&gt;
&lt;h2&gt;The old release path&lt;/h2&gt;
&lt;p&gt;The previous flow started from a GitHub release:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;mermaid&quot;&gt;&lt;pre class=&quot;language-mermaid&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;flowchart&lt;/span&gt; TB
  &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; GitHub&lt;span class=&quot;token text string&quot;&gt;[&quot;GitHub&quot;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;direction&lt;/span&gt; TB
    Release&lt;span class=&quot;token text string&quot;&gt;([Release published])&lt;/span&gt;
    Actions&lt;span class=&quot;token text string&quot;&gt;[GitHub Actions runner]&lt;/span&gt;
    Secrets&lt;span class=&quot;token text string&quot;&gt;[(GitHub Secrets)]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; Runner&lt;span class=&quot;token text string&quot;&gt;[&quot;Inside the release job&quot;]&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;direction&lt;/span&gt; TB
      Checkout&lt;span class=&quot;token text string&quot;&gt;[Checkout source]&lt;/span&gt;
      SecretFiles&lt;span class=&quot;token text string&quot;&gt;[Write .secrets files]&lt;/span&gt;
      Build&lt;span class=&quot;token text string&quot;&gt;[Build Docker image]&lt;/span&gt;
      DeployScript&lt;span class=&quot;token text string&quot;&gt;[Run deploy scripts]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; GoogleCloud&lt;span class=&quot;token text string&quot;&gt;[&quot;Google Cloud&quot;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;direction&lt;/span&gt; TB
    Auth&lt;span class=&quot;token text string&quot;&gt;[Authenticate to Google Cloud]&lt;/span&gt;
    Registry&lt;span class=&quot;token text string&quot;&gt;[(Artifact Registry)]&lt;/span&gt;
    CloudRun&lt;span class=&quot;token text string&quot;&gt;[Cloud Run service]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  Release &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Actions
  Actions &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Checkout
  Actions &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Auth
  Secrets &lt;span class=&quot;token arrow operator&quot;&gt;-.-&gt;&lt;/span&gt;&lt;span class=&quot;token label property&quot;&gt;|decoded during workflow|&lt;/span&gt; SecretFiles
  Checkout &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; SecretFiles &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Build &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Registry
  Registry &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; DeployScript &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; CloudRun

  &lt;span class=&quot;token keyword&quot;&gt;style&lt;/span&gt; GitHub &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#f6f8fa&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#57606a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;style&lt;/span&gt; Runner &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#fff8e1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#f9ab00&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;style&lt;/span&gt; GoogleCloud &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#e8f0fe&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#1a73e8&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A simplified version looked like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; release&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;main

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;published&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest

    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Write credentials
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          mkdir -p .secrets
          echo $SERVICE_ACCOUNT_JSON | base64 -d &gt; .secrets/service_account.json
          echo $MAPS_API_KEY | base64 -d &gt; .secrets/maps_api_key.txt&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Authenticate Google Cloud
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; google&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;actions/auth@v2
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;credentials_json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.DEPLOYER_SERVICE_ACCOUNT &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build image
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; uv run container &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;build &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;env prod &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tag $TAG

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Push image
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; uv run container &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;push &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;env prod &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tag $TAG&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This was a valid first release path. The scripts made local and release behavior predictable, and the release event was explicit.&lt;/p&gt;
&lt;p&gt;Still, the workflow carried too much Google Cloud context: secret files, GCP auth, Docker config, image build, registry push. All of it was headed back into Google Cloud anyway.&lt;/p&gt;
&lt;p&gt;So earlier this year I started thinking: why not let Google Cloud do that part?&lt;/p&gt;
&lt;h2&gt;What is Cloud Build?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://cloud.google.com/build&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Cloud Build&lt;/a&gt; is Google Cloud’s managed build and release service. Instead of renting a generic runner somewhere else and then teaching it how to talk to Google Cloud, Cloud Build runs the release work inside the same platform that already owns the runtime, registry, logs, secrets, and IAM.&lt;/p&gt;
&lt;p&gt;A build is defined as a sequence of containerized steps: build the image, push it to Artifact Registry, deploy a new Cloud Run revision, and record the whole story in Cloud Logging. For this project, that made Cloud Build less of a replacement for GitHub Actions and more of a natural release layer for a Google Cloud-native backend.&lt;/p&gt;
&lt;h2&gt;The Cloud Build release path&lt;/h2&gt;
&lt;p&gt;GitHub Actions and Cloud Build have different shapes.&lt;/p&gt;
&lt;p&gt;GitHub Actions thinks in workflows, jobs, hosted runners, marketplace actions, and repository secrets. Cloud Build thinks in triggers, builds, container steps, service accounts, Artifact Registry, Secret Manager, and Cloud Logging.&lt;/p&gt;
&lt;p&gt;If your application runs somewhere else, GitHub Actions may still be the better general-purpose tool. For this backend, Cloud Build made sense because the deployment target was already Cloud Run. The closer the release path got to Google Cloud, the simpler the system became.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;mermaid&quot;&gt;&lt;pre class=&quot;language-mermaid&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;%%{init: {&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;theme&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;base&quot;&lt;/span&gt;,
  &lt;span class=&quot;token string&quot;&gt;&quot;themeVariables&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;fontFamily&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Inter, system-ui, sans-serif&quot;&lt;/span&gt;,
    &lt;span class=&quot;token string&quot;&gt;&quot;lineColor&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#5f6368&quot;&lt;/span&gt;,
    &lt;span class=&quot;token string&quot;&gt;&quot;primaryTextColor&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#202124&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
  &lt;span class=&quot;token string&quot;&gt;&quot;flowchart&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;htmlLabels&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; true,
    &lt;span class=&quot;token string&quot;&gt;&quot;curve&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;basis&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;%%&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;flowchart&lt;/span&gt; LR

  &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; NEW&lt;span class=&quot;token text string&quot;&gt;[&quot;Cloud Build owns build + deploy&quot;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;direction&lt;/span&gt; TB
    Tag&lt;span class=&quot;token text string&quot;&gt;([Git tag vX.Y.Z])&lt;/span&gt; &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Trigger&lt;span class=&quot;token text string&quot;&gt;[Cloud Build trigger]&lt;/span&gt;
    Trigger &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; BuildNew&lt;span class=&quot;token text string&quot;&gt;[Docker build]&lt;/span&gt;
    BuildNew &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; PushNew&lt;span class=&quot;token text string&quot;&gt;[(Artifact Registry)]&lt;/span&gt;
    PushNew &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; DeployNew&lt;span class=&quot;token text string&quot;&gt;[Deploy Cloud Run]&lt;/span&gt;
    SecretManager&lt;span class=&quot;token text string&quot;&gt;[[Secret Manager]]&lt;/span&gt; &lt;span class=&quot;token arrow operator&quot;&gt;-.-&gt;&lt;/span&gt;&lt;span class=&quot;token label property&quot;&gt;|runtime mounts|&lt;/span&gt; DeployNew
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;classDef&lt;/span&gt; old &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#e8f0fe&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#1a73e8&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#174ea6&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;classDef&lt;/span&gt; new &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#e6f4ea&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#34a853&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#137333&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;classDef&lt;/span&gt; awkward &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#fce8e6&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#d93025&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#a50e0e&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;classDef&lt;/span&gt; store &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#fef7e0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#f9ab00&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#8a5c00&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;classDef&lt;/span&gt; secret &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#f3e8fd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#a142f4&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#681da8&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Actions,BuildOld,DeployOld old
  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Trigger,BuildNew,DeployNew new
  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; PushOld,PushNew store
  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; SecretFiles awkward
  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; SecretManager secret

  &lt;span class=&quot;token keyword&quot;&gt;style&lt;/span&gt; NEW &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#f7fff9&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#34a853&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;2px&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;The source stayed on GitHub. The release runner moved into Google Cloud, where the image registry, Cloud Run service, IAM, logs, and runtime secrets already lived.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The core &lt;code class=&quot;language-text&quot;&gt;cloudbuild.yaml&lt;/code&gt; is not complicated:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;substitutions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;_REGION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; europe&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;west3
  &lt;span class=&quot;token key atrule&quot;&gt;_REPOSITORY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; backend&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;images
  &lt;span class=&quot;token key atrule&quot;&gt;_IMAGE_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; audio&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;guide&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;api
  &lt;span class=&quot;token key atrule&quot;&gt;_SERVICE_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; audio&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;guide&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;api
  &lt;span class=&quot;token key atrule&quot;&gt;_RUNTIME_SERVICE_ACCOUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cloud&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;runtime@PROJECT_ID.iam.gserviceaccount.com

&lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gcr.io/cloud&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;builders/docker
    &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; DOCKER_BUILDKIT=1
    &lt;span class=&quot;token key atrule&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; build
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;t
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_REGION&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;docker.pkg.dev/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;PROJECT_ID&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_REPOSITORY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_IMAGE_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;TAG_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;f
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; Dockerfile.api
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; .

  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gcr.io/cloud&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;builders/docker
    &lt;span class=&quot;token key atrule&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; push
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_REGION&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;docker.pkg.dev/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;PROJECT_ID&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_REPOSITORY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_IMAGE_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;TAG_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gcr.io/google.com/cloudsdktool/cloud&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;sdk
    &lt;span class=&quot;token key atrule&quot;&gt;entrypoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gcloud
    &lt;span class=&quot;token key atrule&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; run
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; deploy
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_SERVICE_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;image
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_REGION&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;docker.pkg.dev/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;PROJECT_ID&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_REPOSITORY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_IMAGE_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;TAG_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;region
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_REGION&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;platform
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; managed
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;service&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;account
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_RUNTIME_SERVICE_ACCOUNT&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;port
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;8080&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;allow&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;unauthenticated

&lt;span class=&quot;token key atrule&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_REGION&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;docker.pkg.dev/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;PROJECT_ID&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_REPOSITORY&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;_IMAGE_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;TAG_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; CLOUD_LOGGING_ONLY&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The tag supplies &lt;code class=&quot;language-text&quot;&gt;TAG_NAME&lt;/code&gt;. Cloud Build builds the image, pushes it, and deploys Cloud Run. Build logs live in Cloud Build. Runtime logs live in Cloud Logging. The deployed image lives in Artifact Registry. The IAM model is Google IAM instead of a GitHub secret that impersonates its way into Google Cloud.&lt;/p&gt;
&lt;p&gt;At that point, the release path matched the runtime instead of pretending GitHub was the center of a Google Cloud deployment.&lt;/p&gt;
&lt;h2&gt;Improving the secret situation&lt;/h2&gt;
&lt;p&gt;One useful side effect of moving to Cloud Build was cleaning up how secrets reached the app.&lt;/p&gt;
&lt;p&gt;The backend needed several of them: a Maps API key, a Firebase service account, a Google Cloud service account, and another API key for model-related tooling. None of these were committed to the repository, but the old release path still made GitHub Actions responsible for turning GitHub Secrets into files under &lt;code class=&quot;language-text&quot;&gt;.secrets/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The official security issue name is: &lt;a href=&quot;https://attack.mitre.org/techniques/T1552/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Unsecured Credentials&lt;/a&gt;. In plain English: exposing keys in places they do not need to be. In a normal backend, that is already bad, but in an agent-like backend, it gets much worse. If a tool path can be nudged into reading files, every readable credential becomes another key on the ring.&lt;/p&gt;
&lt;p&gt;This does not make runtime secrets magically harmless. If the app process can read a mounted file, compromised code can read it too. But at least now CI is out of the secret delivery chain and keeps the image build away from runtime credentials.&lt;/p&gt;
&lt;p&gt;Local development still works like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run secrets &lt;span class=&quot;token parameter variable&quot;&gt;--pull&lt;/span&gt;
uv run start --dev-server&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Docker Compose mounts the same directory for local container runs:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./.secrets&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;/code/.secrets&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;ro&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cloud Run gets the files from Secret Manager:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;set&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;secrets
&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; /code/.secrets/gcp/service_account.json=app&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;service&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;account&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  /code/.secrets/maps/maps_api_key.txt=maps&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;api&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The Docker image doesn’t carry secret files anymore.&lt;/strong&gt; Production secrets live in Secret Manager and local development keeps the same app-facing shape.&lt;/p&gt;
&lt;h2&gt;Build identity and runtime identity are different jobs&lt;/h2&gt;
&lt;p&gt;The migration also cleaned up identity.&lt;/p&gt;
&lt;p&gt;Instead of giving a default service account broad powers, we created a dedicated build/deploy account:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;cloud-build-deployer@PROJECT_ID.iam.gserviceaccount.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That account can run builds, push images, deploy Cloud Run, write logs, and act as the runtime service account.&lt;/p&gt;
&lt;p&gt;The Cloud Run service uses a separate runtime account:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;cloud-run-runtime@PROJECT_ID.iam.gserviceaccount.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That account reads only the runtime secrets it needs.&lt;/p&gt;
&lt;h2&gt;A small release rehearsal&lt;/h2&gt;
&lt;p&gt;I kept one pre-production check: a separate Cloud Run service wired to smoke tags. A &lt;code class=&quot;language-text&quot;&gt;smoke-v...&lt;/code&gt; tag deploys the same build shape to the rehearsal service; a &lt;code class=&quot;language-text&quot;&gt;vX.Y.Z&lt;/code&gt; tag deploys production.&lt;/p&gt;
&lt;p&gt;After each deploy, I run one paid end-to-end request with a real Firebase token and a fixed Brandenburg Gate fixture:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;AUDIOGUIDE_AUTH_TOKEN&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;firebase-id-token&gt;&quot;&lt;/span&gt; uv run smoke &lt;span class=&quot;token parameter variable&quot;&gt;--target&lt;/span&gt; smoke&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That is enough for the release flow. The smoke test is not the main story; it is just the guardrail that lets me test the path before touching production.&lt;/p&gt;
&lt;h2&gt;What broke&lt;/h2&gt;
&lt;p&gt;A few things failed in useful ways.&lt;/p&gt;
&lt;p&gt;Cloud Build could not create the trigger until the GitHub repository was connected through the Cloud Build GitHub App. That step is obvious after you know it. Before that, the CLI error just looks like another setup problem.&lt;/p&gt;
&lt;p&gt;The first Docker build failed because the Dockerfile used BuildKit cache mounts:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;dockerfile&quot;&gt;&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; &lt;span class=&quot;token options&quot;&gt;&lt;span class=&quot;token property&quot;&gt;--mount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;type=cache&lt;/span&gt;&lt;/span&gt; ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;BuildKit is Docker’s newer build engine. It enables features like cache mounts, better layer reuse, and more efficient builds. My Dockerfile already relied on it locally, but the Cloud Build Docker step did not enable it by default.&lt;/p&gt;
&lt;p&gt;The fix was one line:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; DOCKER_BUILDKIT=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first Cloud Run deploy failed because of the Secret Manager mount layout. Different secrets needed isolated paths.&lt;/p&gt;
&lt;p&gt;The runtime environment also needed to be explicit. The image had defaults, but production should not depend on whatever happens to be inside a tracked &lt;code class=&quot;language-text&quot;&gt;.env&lt;/code&gt; file. Cloud Build now sets the Cloud Run runtime environment during deploy.&lt;/p&gt;
&lt;p&gt;None of these issues were dramatic. They were normal migration friction, and having a rehearsal service meant I could find them without poking production.&lt;/p&gt;
&lt;h2&gt;The result&lt;/h2&gt;
&lt;p&gt;The final shape is less about individual steps and more about ownership:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;mermaid&quot;&gt;&lt;pre class=&quot;language-mermaid&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;flowchart&lt;/span&gt; TB
  Repo&lt;span class=&quot;token text string&quot;&gt;[GitHub repo]&lt;/span&gt; &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Tag&lt;span class=&quot;token text string&quot;&gt;[Git tag]&lt;/span&gt;
  Tag &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Trigger&lt;span class=&quot;token text string&quot;&gt;[Cloud Build trigger]&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; BuildDeploy&lt;span class=&quot;token text string&quot;&gt;[Cloud Build]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;direction&lt;/span&gt; TB
    Build&lt;span class=&quot;token text string&quot;&gt;[Build container image]&lt;/span&gt;
    Push&lt;span class=&quot;token text string&quot;&gt;[Push image]&lt;/span&gt;
    Deploy&lt;span class=&quot;token text string&quot;&gt;[Deploy Cloud Run revision]&lt;/span&gt;
    Build &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Push &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Deploy
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; GoogleCloud&lt;span class=&quot;token text string&quot;&gt;[Google Cloud]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;direction&lt;/span&gt; TB
    Registry&lt;span class=&quot;token text string&quot;&gt;[(Artifact Registry)]&lt;/span&gt;
    Run&lt;span class=&quot;token text string&quot;&gt;[Cloud Run service]&lt;/span&gt;
    Secrets&lt;span class=&quot;token text string&quot;&gt;[(Secret Manager)]&lt;/span&gt;
    Logs&lt;span class=&quot;token text string&quot;&gt;[(Cloud Logging)]&lt;/span&gt;
    IAM&lt;span class=&quot;token text string&quot;&gt;[IAM]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  Trigger &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Build
  Push &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Registry
  Registry &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Deploy
  Deploy &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Run
  Secrets &lt;span class=&quot;token arrow operator&quot;&gt;-.-&gt;&lt;/span&gt;&lt;span class=&quot;token label property&quot;&gt;|runtime mounts|&lt;/span&gt; Run
  IAM &lt;span class=&quot;token arrow operator&quot;&gt;-.-&gt;&lt;/span&gt; BuildDeploy
  IAM &lt;span class=&quot;token arrow operator&quot;&gt;-.-&gt;&lt;/span&gt; Run
  Run &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Logs

  &lt;span class=&quot;token keyword&quot;&gt;classDef&lt;/span&gt; source &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#f6f8fa&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#57606a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#24292f&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;classDef&lt;/span&gt; build &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#e8f0fe&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#1a73e8&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#174ea6&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;classDef&lt;/span&gt; runtime &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#e6f4ea&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#34a853&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#137333&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;classDef&lt;/span&gt; secret &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#f3e8fd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#a142f4&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#681da8&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;classDef&lt;/span&gt; store &lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token property&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#fef7e0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#f9ab00&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;1.5px&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;#8a5c00&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Repo,Tag source
  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Trigger,Build,Push,Deploy build
  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Run,Logs,IAM runtime
  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Secrets secret
  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Registry store&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;GitHub still owns the source. Cloud Build owns build and deploy. Artifact Registry owns the image. Cloud Run owns the runtime. Secret Manager owns the secrets. IAM owns the permissions.&lt;/p&gt;
&lt;p&gt;For a small backend, this is the shape I wanted: no platform-engineering ceremony, no maze of console clicks, and no release runner pretending to be the cloud it deploys into.&lt;/p&gt;
&lt;h2&gt;The takeaway&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a30af865f5ef9776413423ec7c43b7a8/35766/cloud-build-migration-release-flow.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACRElEQVR42g2N2W7aQABF/fNV1KpS+9AqVVqihJaQQEMpi42NZ7yPMZ4ZL+N4xWAbAk0TUeUPinRfztGRLufB9hoPj6XxXFoMDbHaR/MbSzqt64CeA3snNIS2LXWceccW21UwO27MwvnlgyuuCYUyBBjNFgZvKOPMV/41uAwVD02JOcLmaKkN10zZREq4GCLlxtV7xOxlZNowkdsyEU7a0VJwtFFElW1qvm5JlS4SKg8HbSDcph54rvDLlmLXdG1AlzAkakz5x3jO7SJR4rtVYtGl4mG9ifUykIVJ78v5h29X1xety0+fPzr6tM6snBkRAXmoJx7c5cY+hdyGjJpAWjlTZg4LIv5dO8Q1hPHt2ds3nd6g1e6+e38GhLs80FIi23DgWXxg8ysf7mPIBcY9scYpFkNH8BazJlZzZlJrete9/No673y/UKX7U/2y8SJHjYhcpeh1Hxwb75DqXLH4Xbp8HcKawSoA+1Q7JIDAjjK97vd/LEAv0G4TNDjW+LDCD0SOCTju6LGhh0Tl6hDUzNiEdkHMx3T5sqaZK1N9siIQQd63pQQLGeUzX2SE991J6klVrFaxsvZ5jlnDzJU2DMXOvH5AjzlB8tzVxTJEDpQKTwupQbEeeyAiQh7NA3eUs3mVqGUw40rCJ47sWTJz4IqhP7FauFKMpALDeAkzqm4etP3KPl2VkbyKQB6IT2vrUBg1k7hsMUgw76JZsJSzUN/hn5ndz/F4G83WTIqpsEuUQ6rsEnWfG7tU26fqU66dzDaY/gdF3QMfyIyMgAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/a30af865f5ef9776413423ec7c43b7a8/577d7/cloud-build-migration-release-flow.webp 300w,
/static/a30af865f5ef9776413423ec7c43b7a8/93ccb/cloud-build-migration-release-flow.webp 600w,
/static/a30af865f5ef9776413423ec7c43b7a8/1aa16/cloud-build-migration-release-flow.webp 1200w,
/static/a30af865f5ef9776413423ec7c43b7a8/e1a3b/cloud-build-migration-release-flow.webp 1672w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/a30af865f5ef9776413423ec7c43b7a8/37b9b/cloud-build-migration-release-flow.png 300w,
/static/a30af865f5ef9776413423ec7c43b7a8/ab3c7/cloud-build-migration-release-flow.png 600w,
/static/a30af865f5ef9776413423ec7c43b7a8/5c837/cloud-build-migration-release-flow.png 1200w,
/static/a30af865f5ef9776413423ec7c43b7a8/35766/cloud-build-migration-release-flow.png 1672w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/a30af865f5ef9776413423ec7c43b7a8/5c837/cloud-build-migration-release-flow.png&quot;
            alt=&quot;Cloud Build migration and release flow illustration&quot;
            title=&quot;A picture is worth a thousand words.&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;A picture is worth a thousand words.&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;Having this centralization gives a lot of peace of mind. When something fails, I do not have to jump between a bunch of different services. So, I can actually vouch for Google Cloud Build. It’s a stable, straightforward solution that made implementing this workflow much simpler, and I can definitely recommend it, especially if you’re already using something like Cloud Run.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[My 26-Q1 Agentic Coding Workflow]]></title><description><![CDATA[Cover: The gang and I building an overengineered CLI. I’ve been doing more and more agentic-coding for the past year. And some patterns keep…]]></description><link>https://iurysouza.dev/my-agentic-coding-workflow-26q1/</link><guid isPermaLink="false">https://iurysouza.dev/my-agentic-coding-workflow-26q1/</guid><pubDate>Thu, 29 Jan 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Cover: The gang and I building an overengineered CLI.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I’ve been doing more and more &lt;em&gt;agentic-coding&lt;/em&gt; for the past year. And some patterns keep showing up. Not rules, just things that seem to work for me across the board.&lt;/p&gt;
&lt;h2&gt;Closing the Loop&lt;/h2&gt;
&lt;p&gt;The agent needs to verify what it built. Not just write code and hope. But actually check if it works. Like a self healing mechanism.&lt;/p&gt;
&lt;p&gt;For an app, that could mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Build the code&lt;/li&gt;
&lt;li&gt;Lint/format&lt;/li&gt;
&lt;li&gt;Run tests&lt;/li&gt;
&lt;li&gt;Run the app&lt;/li&gt;
&lt;li&gt;Inspect the UI&lt;/li&gt;
&lt;li&gt;Debug failures&lt;/li&gt;
&lt;li&gt;Check the logs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You need tooling that lets the agent execute and observe. Sometimes that means writing a small script, sometimes it’s just about adding proper instructions to its context. Sometimes it’s a browser automation tool, and sometimes you build a custom skill, like I did with &lt;a href=&quot;https://github.com/iurysza/android-use/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;android-use&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Closing the agent loop is something I’m really leaning into now. Basically, an agent needs to be able to:
Given a goal, it will derive a plan. Now, it needs to get the world state, then reason on what action to take to achieve that goal, make an action, and &lt;strong&gt;VERIFY&lt;/strong&gt; the state again. Then loop.&lt;/p&gt;
&lt;p&gt;Without being able to verify what happened it can easily get lost. It’s not its “fault” if you think about it, you gotta help it out.&lt;/p&gt;
&lt;p&gt;We’ve briefly talked about that on our &lt;a href=&quot;https://fragmentedpodcast.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Fragmented podcast&lt;/a&gt; last week, skip to 09:03.&lt;/p&gt;
&lt;iframe height=&quot;200px&quot; width=&quot;100%&quot; frameborder=&quot;no&quot; scrolling=&quot;no&quot; seamless src=&quot;https://player.simplecast.com/aa0766a9-30bd-4a71-bb18-f83e15ef9455?dark=true&quot;&gt;&lt;/iframe&gt;
&lt;h2&gt;Multitasking&lt;/h2&gt;
&lt;p&gt;Agents take time. While one’s working, you may jump to something else. Maybe a different part of the codebase. Maybe a different project.&lt;/p&gt;
&lt;p&gt;I try to keep my parallel work under three different things. Backend change here, frontend tweak there, docs update somewhere else. When your system is modular enough, you can hop between them without much hassle.&lt;/p&gt;
&lt;p&gt;It’s oddly addictive. There’s always something you could be doing, but BEWARE. This can fry your brain faster than manual coding+multitasking would.&lt;/p&gt;
&lt;h2&gt;You Don’t Have to Read All the Code&lt;/h2&gt;
&lt;p&gt;Well, it depends on what you’re doing. Please don’t do it on your banking app or in any critical system. I’d like my planes flown without vibe coding for now, thank you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But ON SOME INSTANCES, doing that is fine.&lt;/strong&gt; Think of your home automation scripts, or some fun side project for tracking your run stats.&lt;/p&gt;
&lt;p&gt;The thing is, if you structure things right, you really don’t have to read it all. Focus on the boundaries. Make sure data formats and validation between modules are sound, then &lt;strong&gt;test&lt;/strong&gt; and trust the interior.&lt;/p&gt;
&lt;p&gt;Like a staff eng would trust his/hers peers to implement something, you might eventually build enough trust that if your agents have built and tested like you asked them to, things will work. Also, there’s absolutely no reason to avoid writing tests anymore. Use that.&lt;/p&gt;
&lt;p&gt;This focus on bondaries idea maps to &lt;a href=&quot;https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;functional core, imperative shell&lt;/a&gt;. Which to me always sounded like a nimbler version of hexagonal arch. Separate calculation from action:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Functional Core&lt;/strong&gt;: Pure functions, immutable data. Takes input, returns output. No side effects. No touching the outside world.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Imperative Shell&lt;/strong&gt;: Thin layer that does the I/O. Reads user input, calls the core for logic, writes to database, updates UI.&lt;/p&gt;
&lt;p&gt;Side effects live in the shell. Logic lives in the core. Testing becomes straightforward. So does working with agents (and humans).&lt;/p&gt;
&lt;h2&gt;Semantic Maps&lt;/h2&gt;
&lt;p&gt;I have a folder I call &lt;code class=&quot;language-text&quot;&gt;.ai-artifacts&lt;/code&gt;. It sits at the root and at relevant units throughout the project.&lt;/p&gt;
&lt;p&gt;A unit is whatever has enough meaning to you. Could be the project root, a package, a module, or a feature folder. Like unit tests, you define what this boundary means. I could probably write a full article on the things I like to keep in here.&lt;/p&gt;
&lt;p&gt;Inside each &lt;code class=&quot;language-text&quot;&gt;.ai-artifacts&lt;/code&gt; folder, the main file is the &lt;strong&gt;semantic map&lt;/strong&gt;. It describes what’s in that unit.
Everything you’d want an agent to know before touching this code.
In my case, they can look like &lt;a href=&quot;https://github.com/iurysza/android-use/blob/main/.ai-artifacts/SEMANTIC_MAP.md&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;this&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s independent from the global &lt;code class=&quot;language-text&quot;&gt;AGENTS.md&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt;, so I control when they load. When I want deeper context about &lt;code class=&quot;language-text&quot;&gt;src/auth/&lt;/code&gt;, I run &lt;code class=&quot;language-text&quot;&gt;/load-semantic-map&lt;/code&gt; to inject that folder’s map plus parent folders up to root.&lt;/p&gt;
&lt;p&gt;I also have an agent to create or update a semantic-map. It uses git hash to check if anything changed. Generates new maps or updates existing ones.
They’re opt-in. I decide when the context is worth the tokens.&lt;/p&gt;
&lt;p&gt;OpenCode is building something similar:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-theme=&quot;dark&quot; data-dnt=&quot;true&quot; align=&quot;center&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;This is pretty cool to see!&lt;br&gt;&lt;br&gt;I was manually doing by combining claude rules and a few commands to generate a &amp;quot;semantic mind map&amp;quot; for each &amp;quot;unit&amp;quot; of the project.&lt;br&gt;&lt;br&gt;It worked pretty well. Having it baked into the harness is awesome. &lt;a href=&quot;https://t.co/mWwCddfxq8&quot;&gt;https://t.co/mWwCddfxq8&lt;/a&gt;&lt;/p&gt;&amp;mdash; iury souza (@IurySza) &lt;a href=&quot;https://twitter.com/IurySza/status/2016473746601595332?ref_src=twsrc%5Etfw&quot;&gt;January 28, 2026&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;br&gt;
&lt;p&gt;They also just added a &lt;code class=&quot;language-text&quot;&gt;/learn&lt;/code&gt; command to capture session insights back into scoped context files. Which is similar to what I do when I update the semantic-map.&lt;/p&gt;
&lt;p&gt;We might be on to something.&lt;/p&gt;
&lt;h2&gt;Specialized Agents&lt;/h2&gt;
&lt;p&gt;I’m playing with different agent types for specific areas of work. I don’t use all of them all the time, but these are the categories where I’ve found it useful to spawn a dedicated agent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Coder&lt;/strong&gt;: Main agent. Writes most code, very basic skills/tools, and perhaps an MCP for connecting to the IDE of choice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Planner&lt;/strong&gt;: Read-only mode. Explores codebase, designs approach before implementation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reviewer&lt;/strong&gt;: Fresh eyes after changes are done. This guy is a PITA.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tester&lt;/strong&gt;: Writes tests for what Coder built. Picky. Looks for edge cases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Researcher&lt;/strong&gt;: Connected to a few MCPs to find info online. Meant for scouring the web, also has a browser-tool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scribe&lt;/strong&gt;: Maintains architecture docs and semantic maps.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of having every skill and MCP globally available, each agent gets equipped with just the subset it needs.
Minimal tooling per agent. Focused scope, fewer ways to go wrong.
I manually spawn these. No automatic orchestration, I decide when I need a different perspective.&lt;/p&gt;
&lt;h2&gt;The agentic trap&lt;/h2&gt;
&lt;p&gt;Don’t overdo it. You don’t need an army of subagents. If you’re not at the frontier of this, I recommend avoiding getting sucked into this rabbit hole.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-theme=&quot;dark&quot; data-dnt=&quot;true&quot; align=&quot;center&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Im leaning towards this opinion for some time.&lt;br&gt;Occam&amp;#39;s razor, man... &lt;a href=&quot;https://t.co/4kqObYCTFk&quot;&gt;https://t.co/4kqObYCTFk&lt;/a&gt; &lt;a href=&quot;https://t.co/yQrj9XB6wI&quot;&gt;pic.twitter.com/yQrj9XB6wI&lt;/a&gt;&lt;/p&gt;&amp;mdash; iury souza (@IurySza) &lt;a href=&quot;https://twitter.com/IurySza/status/2018249305585283220?ref_src=twsrc%5Etfw&quot;&gt;February 2, 2026&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;br&gt;
&lt;h2&gt;Workflow High level View&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start on &lt;code class=&quot;language-text&quot;&gt;plan-mode&lt;/code&gt;:&lt;/strong&gt; describe what you want in plain English using a lot of context and as much detail as you can.
&lt;ul&gt;
&lt;li&gt;protips:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.aihero.dev/plan-mode-introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Build your own&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Use dictation tools for this talking to the model here. There are tons of options, I’ve settled with &lt;a href=&quot;https://github.com/Beingpax/VoiceInk&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;VoiceInk&lt;/a&gt; some time ago.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iterate:&lt;/strong&gt; Iterate on that plan as much as needed. Get an output file. Open it. Read through.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;/tracker&lt;/code&gt; - Use a command/skill/prompt to break down the work into phases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code:&lt;/strong&gt; Code it however you prefer. Serial or parallel (don’t overdo it), human in the loop or not.
&lt;ul&gt;
&lt;li&gt;You can have one instance coding, another reviewing, another writing tests. Or do one thing at a time.&lt;/li&gt;
&lt;li&gt;protip: You don’t need the SOTA model to do all of the work (unless someone else is paying for it.). Find a balance of what tasks can be offloaded to faster/cheaper agents. I’m in a &lt;code class=&quot;language-text&quot;&gt;kimi2.5-coding&lt;/code&gt; + &lt;code class=&quot;language-text&quot;&gt;GPT-5.2-Codex&lt;/code&gt; with &lt;code class=&quot;language-text&quot;&gt;opencode&lt;/code&gt; phase now (jan 26).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manage the context:&lt;/strong&gt; Know when to clear the context and &lt;em&gt;start over&lt;/em&gt;. above 100k context is iffy territory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other day I found out there’s a framework that kinda emcompasses a lot of this. They call it &lt;a href=&quot;https://github.com/github/spec-kit&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;spec-kit&lt;/a&gt;.
I’m not a fan of such frameworks yet because I don’t like losing control of how this works, but I like it when they show that my experience is somewhat aligned with them.&lt;/p&gt;
&lt;p&gt;Confirmation bias, am I right?&lt;/p&gt;
&lt;p&gt;In any case, the concept is very simple and powerful: You just gotta build good enough context (md files) for the agent to keep working.&lt;/p&gt;
&lt;figure style=&quot;text-align:center;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 29.000000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAIAAABM9SnKAAAACXBIWXMAAAsTAAALEwEAmpwYAAABR0lEQVR42hXHR3KCUAAAUO5/CbNKcgA1YwtIRyHSlCIIiGBBUNqn+NEkM2/zEN8LQi2OrtHlfI6TtEhvZZ7ckixL0ywrirzI87wC1aNpm7qFsIuji23oRdWetAWC8+4XYRHCYUTtZqQ8Io0x7TBiOCTsCaFgnNXHDOLHH+LWBF+jtD7ALVLY9ymf+UaRpeJNKYVeiihnzebsnNMxRubFNS16JMczgjGlVF5SyJXD8CIjqOjC5kR7zDr8fIYAkLR1BFLh2d06mD5fLcgdcJe2G38j7SEENbhUKX9wQmnpFlkGYVlXCXx1V4tF6qNcmlh7819l+AeGYmnT7T04b81A3z4uem5g8L6PXdtTjepoliZaB1LtUNFqgOTqNMZ7jcfD47o7aUAe3tn3er9qA+kZaQ+LiIm3asc2/10/XDaa94A2K5afV/rjF1x8NxkbrjBFAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/54125f9fbbf0e7d14c71358904f3d801/577d7/spec-kit.webp 300w,
/static/54125f9fbbf0e7d14c71358904f3d801/93ccb/spec-kit.webp 600w,
/static/54125f9fbbf0e7d14c71358904f3d801/1aa16/spec-kit.webp 1200w,
/static/54125f9fbbf0e7d14c71358904f3d801/daa23/spec-kit.webp 1800w,
/static/54125f9fbbf0e7d14c71358904f3d801/b1e4f/spec-kit.webp 2400w,
/static/54125f9fbbf0e7d14c71358904f3d801/2ba52/spec-kit.webp 3706w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/54125f9fbbf0e7d14c71358904f3d801/37b9b/spec-kit.png 300w,
/static/54125f9fbbf0e7d14c71358904f3d801/ab3c7/spec-kit.png 600w,
/static/54125f9fbbf0e7d14c71358904f3d801/5c837/spec-kit.png 1200w,
/static/54125f9fbbf0e7d14c71358904f3d801/2f4db/spec-kit.png 1800w,
/static/54125f9fbbf0e7d14c71358904f3d801/84976/spec-kit.png 2400w,
/static/54125f9fbbf0e7d14c71358904f3d801/ee11e/spec-kit.png 3706w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/54125f9fbbf0e7d14c71358904f3d801/5c837/spec-kit.png&quot; alt=&quot;Spec-Kit workflow&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Spec-Kit workflow&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;Hope this is useful!&lt;/p&gt;
&lt;p&gt;PS: This will likely change before next month :D&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Craftsman's Case for AI]]></title><description><![CDATA[“Craftsmanship starts with the tools. Your setup shapes the work as much as your skill.” Software is no different. I stumbled onto the idea…]]></description><link>https://iurysouza.dev/craftsmanship-ai-coding/</link><guid isPermaLink="false">https://iurysouza.dev/craftsmanship-ai-coding/</guid><pubDate>Tue, 20 Jan 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;“Craftsmanship starts with the tools. Your setup shapes the work as much as your skill.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Software is no different.&lt;/p&gt;
&lt;p&gt;I stumbled onto the idea of software craftsmanship very early on in my career. Probably something I picked from the Agile dinosaurs back when I started coding. Whatever you think of the figures behind it, the concept stuck with me. A clear north star: get good at your craft. Treat code like a trade worth mastering. That felt compelling then. It still does.&lt;/p&gt;
&lt;h2&gt;The Toolbox&lt;/h2&gt;
&lt;p&gt;One piece of this puzzle was getting good at the &lt;strong&gt;tools&lt;/strong&gt; you use. Historically, this meant mastering the &lt;a href=&quot;https://en.wikipedia.org/wiki/Unix_philosophy&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Unix philosophy&lt;/a&gt;. It meant preferring the terminal and the CLIs, for direct manipulation of the file system, the actual state of the computer, rather than a proxied UI layer. It meant knowing IDE shortcuts and some obscure settings.&lt;/p&gt;
&lt;p&gt;Also, craftsmanship involves ownership. You shouldn’t rely just on the tools your OS or your company provides. You need a personal toolbox which can travel with you from job to job and computer to computer. It’s the accumulation of your specific workflow optimizations over years.&lt;/p&gt;
&lt;h2&gt;Opportunity Cost&lt;/h2&gt;
&lt;p&gt;Until recently, there was a hard limit to this toolbox. We collected aliases and configurations, but we rarely built full-fledged software to solve our own minor inconveniences. The opportunity cost was simply too high.&lt;/p&gt;
&lt;p&gt;I’ve already &lt;a href=&quot;../agentic-coding-and-context-engineering&quot;&gt;written&lt;/a&gt; about how AI coding unlocks us for building tools that can automate tedious workflows at our workplaces, improving internal tooling and what not. But another point where I think there’s &lt;strong&gt;massive&lt;/strong&gt; leverage is in improving our personal workflows.&lt;/p&gt;
&lt;p&gt;You can script complex automations for your specific development environment that maybe no one else needs, but which make you significantly more effective. Because ideally, software should fit you like a craftsman’s tool fits their hands. Now that code is relatively cheaper, we can finally do more of that.&lt;/p&gt;
&lt;h2&gt;Compounding&lt;/h2&gt;
&lt;p&gt;I’ve been curating my &lt;a href=&quot;https://wiki.archlinux.org/title/Dotfiles&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;dotfiles&lt;/a&gt; for many years now. Aliases, neovim setup, zsh tweaks, tmux keybindings and much much more. Everything looks and feels very familiar to me. You spend enough time somewhere, you make it yours.&lt;/p&gt;
&lt;p&gt;But that used to evolve really slowly. It would take months for me to even attempt to adopt some tool or to try and customize a workflow. Now, it takes me minutes. I can spin up an agent and set things up in no time. Undo, change, integrate with other tools, anything is possible. And it’s fast.&lt;/p&gt;
&lt;p&gt;Besides, I can now create small tools or scripts, and build experiments like &lt;a href=&quot;../shadow-claude.md&quot;&gt;Shadow Claude&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another thing I built recently was a window/workspace manager for macOS, which I was never happy with (having tried many alternatives over the years). I spent an afternoon with an agent and built exactly what I needed: &lt;a href=&quot;https://github.com/iurysza/ultra&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;iurysza/ultra&lt;/a&gt;. Just portable, simple, mine.&lt;/p&gt;
&lt;p&gt;These are not that impressive, I know. But these things compound nicely. Over time what you get is a system that’s familiar, which can massively augment your capacity to investigate a bug, to analyze some state, to have a clear grip of what’s going on.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To seamlessly jump between different parts of a system in a smooth and natural way. Tasks that require multiple steps can be reduced to a single command. This composition is the real unlock.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Nerd Sniping&lt;/h2&gt;
&lt;p&gt;There is one major caveat. With the barrier to entry lowered, the risk of nerd sniping yourself increases.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 740px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0410ac827bb193c12c4312ed8bc3add7/e052b/nerd_sniping.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABfklEQVR42jWRW4+CMBCF+/9/ji8+aqIxQQEBbRVBBQUFcb2Al3XVuF9onIemnc45c86MCMPQ87zpdCql5H4+nzlns9l8PldKZVn2eDwulwv5/X6/3W6jOA7CcDKZJEkiyrKkotvtgs/z/Ha7BUEwGo343u12p9Ppfr9Tw1lVFQDLNIf9vq9UWVVC81E6Ho8BHw6Hnzqoq+ooioIa8HxkeW4bxtA0EUVG2LbdarVc10X2YDCglCbH49EwDBi50NyyrE6nA2Oapq7nSaUopqVwHKfX6+GQN8qv1yvdENxut7WR5XLZbDYhwrYWEscxRPQQcANAw+83/up4Pp/v95tTJ7loOwyFJ18QCaaFPTrTB4rP58NJw/V6HdeBYZLMnPtisUBUFEUgyYvVauX7PrsBjD22gsLNZgM9FYhCHoK1tNfrhVVGQDEbEejhDz3QUwcrrvBJEQDAMLJ53Z+2YHCKbIYnGDqtmBbNkaqJgFFX1MGFDAD2z2oajQZ4nEop/wFXHRrhZLehxQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/0410ac827bb193c12c4312ed8bc3add7/577d7/nerd_sniping.webp 300w,
/static/0410ac827bb193c12c4312ed8bc3add7/93ccb/nerd_sniping.webp 600w,
/static/0410ac827bb193c12c4312ed8bc3add7/86d93/nerd_sniping.webp 740w&quot;
              sizes=&quot;(max-width: 740px) 100vw, 740px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/0410ac827bb193c12c4312ed8bc3add7/37b9b/nerd_sniping.png 300w,
/static/0410ac827bb193c12c4312ed8bc3add7/ab3c7/nerd_sniping.png 600w,
/static/0410ac827bb193c12c4312ed8bc3add7/e052b/nerd_sniping.png 740w&quot;
            sizes=&quot;(max-width: 740px) 100vw, 740px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/0410ac827bb193c12c4312ed8bc3add7/e052b/nerd_sniping.png&quot;
            alt=&quot;xkcd356: Nerd Sniping&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;xkcd356: Nerd Sniping&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;Just because you can build a tool for everything doesn’t mean you should. It’s now even easier to fall into the trap of spending hours building things just because.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;You are no longer limited to the tools you can buy or the scripts you have time to painstakingly write. If you treat your development environment as a product you own, AI allows you to forge exactly the tools you need to be a master of your craft.&lt;/p&gt;
&lt;p&gt;That’s honestly one of the most compelling use cases of AI-coding to me. The meta layer. Improving your ability to do your work.
This also helps to approach another major point now: developer grief in the face of these changes. But that’s the topic for another article.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Non-Deterministic Interfaces or How AI is Coming for the Frontend]]></title><description><![CDATA[xkcd: Can’t wait to find the intersection of debugging and philosophy Last month the Flutter team dropped a GenUI SDK. The idea is you…]]></description><link>https://iurysouza.dev/non-deterministic-interfaces-ai-is-coming-for-the-frontend/</link><guid isPermaLink="false">https://iurysouza.dev/non-deterministic-interfaces-ai-is-coming-for-the-frontend/</guid><pubDate>Mon, 29 Dec 2025 22:51:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://xkcd.com/3134&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;xkcd:&lt;/a&gt; Can’t wait to find the intersection of debugging and philosophy&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Last month the Flutter team dropped a &lt;a href=&quot;https://docs.flutter.dev/ai/genui&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GenUI SDK&lt;/a&gt;. The idea is you register a widget library, give an LLM the component definitions and let it decide what to render and with what data.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;At its core, the GenUI SDK for Flutter is an orchestration layer. This suite of packages coordinates the flow of information between your user, your Flutter widgets, and an AI agent, transforming text-based conversations into rich, interactive experiences.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And it’s &lt;strong&gt;not about Flutter&lt;/strong&gt;. &lt;strong&gt;Vercel’s AI SDK&lt;/strong&gt; first announced GenUI support back in &lt;strong&gt;March 2024&lt;/strong&gt;.
Google is &lt;a href=&quot;https://research.google/blog/generative-ui-a-rich-custom-visual-interactive-user-experience-for-any-prompt/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;experimenting&lt;/a&gt; with that on search and the Gemini App. They recently published this interesting &lt;a href=&quot;https://generativeui.github.io/static/pdfs/paper.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;paper&lt;/a&gt; on the topic.&lt;/p&gt;
&lt;p&gt;Server-driven UI, but the server is now a language model. I don’t think this is particularly revolutionary as a concept, but IMO they’re signals.&lt;/p&gt;
&lt;p&gt;Something’s cooking.&lt;/p&gt;
&lt;h3&gt;The chat interface ceiling&lt;/h3&gt;
&lt;p&gt;We’ve been interacting with LLMs through chat windows for a few years now. Text in, text out. Then we got multimodal input/output, but still very limiting. Markdown became the de facto UX improvement, and if you’re lucky you get a &lt;a href=&quot;https://mermaid.js.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Mermaid&lt;/a&gt; diagram integration here and there.&lt;/p&gt;
&lt;p&gt;It works. But it’s also obviously limiting. The LLMs already are good at generating structured information (structured output). Why are we constraining the output to a text box?&lt;/p&gt;
&lt;p&gt;The logical next step is letting the model decide not just the content, but the presentation. Which components to render. How to lay them out. Based on the query, the context, whatever signals are available.&lt;/p&gt;
&lt;p&gt;This isn’t a prediction. It’s already happening in various forms.&lt;/p&gt;
&lt;p&gt;Take Claude Code’s &lt;code class=&quot;language-text&quot;&gt;AskUserQuestion&lt;/code&gt; tool. A criminally underrated feature (which every other agentic coding tool was quick to copy). It’s so simple and illustrates this idea well:&lt;/p&gt;
&lt;p&gt;Instead of a lot of back and forth with the agent, I ask it to generate a multi-step wizard UI on the fly. Here’s a snippet of how I use it with a simple &lt;code class=&quot;language-text&quot;&gt;gemini-image-gen&lt;/code&gt; skill I built.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;md&quot;&gt;&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; [!IMPORTANT] YOU MUST USE AskUserQuestion tool for gathering user feedback before starting generation.
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; The questions should attempt to cover the parameter space for the given task. Identify the key dimensions, constraints, and optimization targets that a specialist would consider when creating such an image.
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; After gathering the feedback from the user you must launch another AskUserQuestion round, but this time offering suggestions that should attempt to offer enough variability to the output, but still adhering to the confirmed constraints. One of the suggestions should be a bit out of the box.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What you get is this:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/499ad42babb116dfb32e4538caf9be87/20aaf/claude-code-non-deterministic-ux.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB/UlEQVR42n2T23abMBRE/S9tbMdchARIIAQGHF9WstqnPCa/kd+fzpEvcbqaPswSAmlrzhyx2D8dIDodn/G0Oz8fDycc9ieEMKKuO6RpSVXI0hqKKjKLgmPGd5ukxDoxeOSan8sUi20/Y9ruIGPUMGMYdtSMth2QCCi33GjiZpGANaGinN8f74HOdJjCDn07YuhmBMr7EW03wYcZthlQlG10cZUA0qSKQJM7JDzkBrTGI7gt2iqg1p4LuWGjsRYlGqtNESXPf0tARjlCbawkAhvbwVctBjrsm/ECJOAC+g52g9JZrRrkWY0fywSL3p/wcnqjyxklTzLKQikGTiU58yssyzH/leypTYuHVYbF719veH//wMvxFTMzC25gjlPMsHI9PBulTPOtw1V0aSJwKcCGmxqW3DcTpu7IBh3gCS0rH5theG00n69uNll50z24JHC1zrFQtDv0e16dAyaOg2OHSw/DUpV2seSco0jmyrjb/J/AnFl1LG8c9+jDhIawXsq2PSo6VPoTkGt2My+/NEtKjjly38OKXZbwKxvQ+C0cL7JhXpYfBRgYh5PSC3FaY53qL10/g/XZORW7LIGnhMoLGcWFHKIJsUay3aIj3BJcXhzHLOV3zLmOa1ThPi+2fLzP4v46yDxl+Jo5C7Ah2NG1/N8VZXiAHH7dJ8A/XduekdEq7DIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/499ad42babb116dfb32e4538caf9be87/577d7/claude-code-non-deterministic-ux.webp 300w,
/static/499ad42babb116dfb32e4538caf9be87/93ccb/claude-code-non-deterministic-ux.webp 600w,
/static/499ad42babb116dfb32e4538caf9be87/1aa16/claude-code-non-deterministic-ux.webp 1200w,
/static/499ad42babb116dfb32e4538caf9be87/143e3/claude-code-non-deterministic-ux.webp 1410w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/499ad42babb116dfb32e4538caf9be87/37b9b/claude-code-non-deterministic-ux.png 300w,
/static/499ad42babb116dfb32e4538caf9be87/ab3c7/claude-code-non-deterministic-ux.png 600w,
/static/499ad42babb116dfb32e4538caf9be87/5c837/claude-code-non-deterministic-ux.png 1200w,
/static/499ad42babb116dfb32e4538caf9be87/20aaf/claude-code-non-deterministic-ux.png 1410w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/499ad42babb116dfb32e4538caf9be87/5c837/claude-code-non-deterministic-ux.png&quot;
            alt=&quot;Claude Code&amp;#39;s AskUserQuestion tool generating a wizard UI for image creation&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Claude Code’s AskUserQuestion tool generating a wizard UI for image creation&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;The AI reads the context (this blog post), then dynamically generates relevant style options, each with descriptions. A multi-step flow with Style → Palette → Concept → Submit.&lt;/p&gt;
&lt;p&gt;No one coded this specific wizard. The structure emerged from the prompt and context. This is the whole idea in a nutshell.&lt;/p&gt;
&lt;h3&gt;What this actually means&lt;/h3&gt;
&lt;p&gt;Here’s the part that’s been rattling around in my head.&lt;/p&gt;
&lt;p&gt;We’ve spent decades building (or attempting to build) deterministic systems. Same input, same output. That’s the contract. That’s what users expect. That’s what testing assumes.&lt;/p&gt;
&lt;p&gt;Generative UI breaks that contract. Ask the same question twice, and you potentially get different layouts. Different component choices. Different emphasis. The system is working correctly. It’s just that “correctly” now includes probability.&lt;/p&gt;
&lt;p&gt;And I think that’s… fine? For certain problems? Maybe?&lt;/p&gt;
&lt;p&gt;LLMs work best when there’s no single correct answer. When the space is ambiguous.
So that may work for a set of use cases.&lt;/p&gt;
&lt;h3&gt;The interesting bit&lt;/h3&gt;
&lt;p&gt;The implication is that we’ll have products where the interface itself is non-deterministic. Probabilistic by design.&lt;/p&gt;
&lt;p&gt;That raises questions I don’t have answers to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How do you test this? Are we going to start benchmarking user satisfaction?&lt;/li&gt;
&lt;li&gt;What’s the fallback when the generated UI is wrong or confusing?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I suspect the patterns will involve things like user correction loops, graceful degradation to deterministic components, and some form of transparency about uncertainty. But the industry hasn’t figured this out yet. We’re in the “interesting problem” phase.&lt;/p&gt;
&lt;h3&gt;Not everything, obviously&lt;/h3&gt;
&lt;p&gt;Most apps don’t need this. CRUD with well-defined flows doesn’t benefit from probabilistic UI decisions. If you know exactly what the user needs to see, just show it.&lt;/p&gt;
&lt;p&gt;But there’s a class of problems where this might make sense. Where the “right” UI genuinely depends on runtime context that’s hard to enumerate in advance.&lt;/p&gt;
&lt;p&gt;The point isn’t that generative UI is the future of all frontends. The point is that it seems to have found a legitimate niche. And that niche implies a shift in how we think about building certain kinds of products.&lt;/p&gt;
&lt;h3&gt;The stack is splitting&lt;/h3&gt;
&lt;p&gt;There’s an &lt;a href=&quot;https://www.youtube.com/watch?v=x-01UrScIrA&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;interesting framing&lt;/a&gt; that’s been floating around that talks about how &lt;code class=&quot;language-text&quot;&gt;some software&lt;/code&gt; could be decoupled into three layers.&lt;/p&gt;
&lt;p&gt;At the bottom, you have the &lt;strong&gt;system of record&lt;/strong&gt;, your data models, APIs, etc. The durable stuff that doesn’t change much. Above that you’d have an &lt;strong&gt;agentic layer&lt;/strong&gt; that would abstract the application logic and at the very end you would have a &lt;strong&gt;generative UI layer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Pixels generated at runtime for a specific user and goal. A one-off chart. A temporary decision panel, etc.&lt;/p&gt;
&lt;p&gt;The interesting implication: if the interface layer becomes ephemeral, in that scenario you would stop building screens and start curating component libraries that agents can assemble on demand. Weird.&lt;/p&gt;
&lt;h3&gt;Weird times&lt;/h3&gt;
&lt;p&gt;We’re quietly moving toward a world where some products are non-deterministic by design. And that is really weird.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Memory Portability: Owning what matters]]></title><description><![CDATA[What if your AI assistant could remember everything you’ve told it? Across sessions, across tools, across providers. Not locked into one…]]></description><link>https://iurysouza.dev/memory-portability-owning-your-ai-context/</link><guid isPermaLink="false">https://iurysouza.dev/memory-portability-owning-your-ai-context/</guid><pubDate>Mon, 22 Dec 2025 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What if your AI assistant could remember everything you’ve told it? Across sessions, across tools, across providers. Not locked into one company’s servers. Portable. Yours.&lt;/p&gt;
&lt;h2&gt;Why This Matters&lt;/h2&gt;
&lt;p&gt;I’m not sure who’s winning the LLM wars. But I know this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If I’d been feeding ChatGPT my data for the past two or three years, you probably couldn’t pay me to switch now.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The more it knows you, the more indispensable it gets. The friction to change providers becomes enormous.&lt;/p&gt;
&lt;p&gt;Sure, people stay with providers for other reasons: model quality, integrations, habit, pricing. But those factors shift. A better model launches. Prices drop. APIs get abstracted away. Memory is different. It compounds over time. The longer you use a system, the more context it accumulates, and the harder it becomes to replicate that elsewhere. That’s what makes it the stickiest form of lock-in. Almost like a natural monopoly.&lt;/p&gt;
&lt;h2&gt;The Manual Approach&lt;/h2&gt;
&lt;p&gt;I’ve been trying to maintain multiple system prompts: one for health data, one for financial context, some for general coding preferences. I share them across multiple providers/clients: Gemini, Claude, OpenAI, &lt;a href=&quot;https://msty.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Msty Studio&lt;/a&gt;, Raycast, AI Studio, etc. Each context file gets tweaked over time as I learn what works and the data evolves.&lt;/p&gt;
&lt;p&gt;It’s obviously tedious, but provides enough benefit that I keep doing it. Still, every time I switch tools or start fresh, I need to surface that context somehow. Each tool has a way to inject relevant context, and sometimes you just have to paste it. There’s gotta be a better way.&lt;/p&gt;
&lt;p&gt;I remember reading about memory layer tools like &lt;a href=&quot;https://mem0.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Mem0&lt;/a&gt; back in 2024. At the time it felt like too much work, maybe targeting devs building agents.&lt;/p&gt;
&lt;p&gt;So I started thinking: what would a basic memory system look like? Something I could prototype using the hook system from my &lt;a href=&quot;/shadow-claude&quot;&gt;Shadow Claude&lt;/a&gt; project?&lt;/p&gt;
&lt;h2&gt;The Memory Model Basics&lt;/h2&gt;
&lt;p&gt;I sketched out what a minimal memory system would need. Two core flows:&lt;/p&gt;
&lt;h3&gt;Memory Storage&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;mermaid&quot;&gt;&lt;pre class=&quot;language-mermaid&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;%%caption: Hooks to capture memories and store as vectors&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;flowchart&lt;/span&gt; TB
    &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; Capture&lt;span class=&quot;token text string&quot;&gt;[&quot;Capture&quot;]&lt;/span&gt;
        S&lt;span class=&quot;token text string&quot;&gt;[Session Ends]&lt;/span&gt; &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; H&lt;span class=&quot;token text string&quot;&gt;[Hook]&lt;/span&gt;
        H &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; LLM&lt;span class=&quot;token text string&quot;&gt;[LLM Summarizer]&lt;/span&gt;
        LLM &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; F&lt;span class=&quot;token text string&quot;&gt;[Structured Facts]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    F &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; &lt;span class=&quot;token label property&quot;&gt;|Append|&lt;/span&gt; TXT

    &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; Storage&lt;span class=&quot;token text string&quot;&gt;[&quot;Storage&quot;]&lt;/span&gt;
        TXT&lt;span class=&quot;token text string&quot;&gt;[(Memory Store)]&lt;/span&gt; &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; V&lt;span class=&quot;token text string&quot;&gt;[Vectorize]&lt;/span&gt;
        V &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; VDB&lt;span class=&quot;token text string&quot;&gt;[(Vector DB)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This summarizes what happened in a session: user preferences, decisions, facts. Vectorize for semantic search.&lt;/p&gt;
&lt;h3&gt;Memory Retrieval&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;mermaid&quot;&gt;&lt;pre class=&quot;language-mermaid&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;%%caption: Sub-agent decomposes requests into multiple memory queries&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;flowchart&lt;/span&gt; TB
    Q&lt;span class=&quot;token text string&quot;&gt;[User]&lt;/span&gt; &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt;&lt;span class=&quot;token label property&quot;&gt;|query|&lt;/span&gt; A&lt;span class=&quot;token text string&quot;&gt;[Main Agent]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; SubAgent&lt;span class=&quot;token text string&quot;&gt;[&quot;Recall Sub-Agent&quot;]&lt;/span&gt;
        SA&lt;span class=&quot;token text string&quot;&gt;[Decompose Request]&lt;/span&gt;
        SA &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Q1&lt;span class=&quot;token text string&quot;&gt;[Query 1]&lt;/span&gt;
        SA &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; Q2&lt;span class=&quot;token text string&quot;&gt;[Query 2]&lt;/span&gt;
        SA &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; QN&lt;span class=&quot;token text string&quot;&gt;[Query N]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    A &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; &lt;span class=&quot;token label property&quot;&gt;|delegates|&lt;/span&gt;SA
    Q1 &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; Q2 &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; QN &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; VDB&lt;span class=&quot;token text string&quot;&gt;[(Vector DB)]&lt;/span&gt;
    VDB &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; AGG&lt;span class=&quot;token text string&quot;&gt;[LLM Aggregates Results]&lt;/span&gt;
    AGG &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; A
    A &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt;&lt;span class=&quot;token label property&quot;&gt;|response|&lt;/span&gt; Q&lt;span class=&quot;token text string&quot;&gt;[User]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A sub-agent could decompose requests and trigger multiple retrieval attempts from different angles. The depth of retrieval scales with memory size. More memories mean more creative search strategies. I know I could use GraphRAG or something similar, but that would perhaps defeat the simplicity goal.&lt;/p&gt;
&lt;h3&gt;Two Use Cases&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Explicit Recall&lt;/strong&gt;: “What’s my marathon time PB?” Direct query, standard RAG.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implicit Augmentation&lt;/strong&gt;: You ask “Who’s playing tonight?” and the agent knows you support a specific team because you mentioned it months ago. Or you ask for a training plan and it factors in your VO2 max, injury history, and recent race times without you having to re-explain your fitness level. No explicit query. The context just shows up when relevant.&lt;/p&gt;
&lt;h2&gt;MCP Memory Service&lt;/h2&gt;
&lt;p&gt;Turns out someone already built this. &lt;a href=&quot;https://github.com/doobidoo/mcp-memory-service&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MCP Memory Service&lt;/a&gt; is an open-source tool that implements this pattern and more.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;mermaid&quot;&gt;&lt;pre class=&quot;language-mermaid&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;flowchart&lt;/span&gt; LR
    &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; acq &lt;span class=&quot;token text string&quot;&gt;[Acquisition]&lt;/span&gt;
        A1&lt;span class=&quot;token text string&quot;&gt;[Session-End Hook]&lt;/span&gt;
        A2&lt;span class=&quot;token text string&quot;&gt;[Skill Call]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; store &lt;span class=&quot;token text string&quot;&gt;[Storage]&lt;/span&gt;
        S1&lt;span class=&quot;token text string&quot;&gt;[Vector DB + Embeddings]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; cons &lt;span class=&quot;token text string&quot;&gt;[Consolidation]&lt;/span&gt;
        C1&lt;span class=&quot;token text string&quot;&gt;[Dream Cycle]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;subgraph&lt;/span&gt; ret &lt;span class=&quot;token text string&quot;&gt;[Retrieval]&lt;/span&gt;
        R1&lt;span class=&quot;token text string&quot;&gt;[Session-Start Hook]&lt;/span&gt;
        R2&lt;span class=&quot;token text string&quot;&gt;[Mid-Conversation]&lt;/span&gt;
        R3&lt;span class=&quot;token text string&quot;&gt;[Skill Call]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    A1 &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; S1
    A2 &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; S1
    S1 &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; C1
    C1 &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; S1
    R1 &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; S1
    R2 &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; S1
    R3 &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; S1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What I liked:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hook-based capture&lt;/strong&gt;: Enables passively capturing memory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No LLMs for extraction&lt;/strong&gt;: The hooks are extractive, not generative. It relies on pattern matching to identify what is worth saving, so it runs often and fast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dream-inspired consolidation&lt;/strong&gt;: Async process that decays old memories, clusters related ones, compresses redundant information&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session-start injection&lt;/strong&gt;: Surfaces relevant context when you start a new session&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local-first&lt;/strong&gt;: SQLite-vec backend with super fast (5ms) reads&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You own the data&lt;/strong&gt;: Everything stays on your machine unless you choose to sync&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The “dream cycle” is a nice touch. Instead of memories accumulating forever, the system periodically consolidates them. &lt;a href=&quot;https://www.nature.com/articles/s41593-019-0467-3&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Like how sleep helps humans consolidate learning&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What I got working&lt;/h2&gt;
&lt;p&gt;I set up MCP Memory Service with Claude Code and Msty Studio. The dashboard lets me browse memories by tag (auto-generated).&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a9490b976c9b58670dc29c8e906c3cd8/f7126/memory-dashboard.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByElEQVR42nVSW3KbQBDkAo6NEwuBJV5iea2ARUISst5WyXFVUrmDP5Nj5Q45X2dmFXA+ko+u2aXYnu6eMabqDDH/hrD+AlEd4KcrDP0KA7eA5ZewPEZFZwXbyxAfvmP34xfWbz9RPn2FWr8iUUcUiwtCuYHhZy1iJso3mEy3cKdnBHKPvD7SfYOUalTsEJd7CEIon5CqHbxkgXG81HATxkpXYyJbyDl1aE5wwhqmk+PeyTRMO32vBPMP7p0Ud8PknzCYhLu5cYNRNMNoMsOnkcTNQ4QPA/Ff3HbVijV6wu4SUHZj0VCGS50jn0W5o0ZkJ14gIvujqEGYr/XdJRETsv9IAjyye2dfVRvchT+q9hV+tib7z9fciEC1n3V+otiipnOYc35HlDSAgIhlc9a5c77slLl6wqjc6u757KR/rlYXUrglQnpQ7XWDcnkBD1HOT/DIRar2+s7DGwZKWzdYpmlnGNBqsLqEJs5K1OoFGSkN/mrS1/pa2UmiDnrFzM4ys358zLUCtsadeTWmZEc2z3q3uAnfOZKMLKv2RatmZ6wyIJU9YTedmwfRT9ajwbB9ixacG41pCA495sqbIIoNbMrs1np/0035N/UEJqw3pL30AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/a9490b976c9b58670dc29c8e906c3cd8/577d7/memory-dashboard.webp 300w,
/static/a9490b976c9b58670dc29c8e906c3cd8/93ccb/memory-dashboard.webp 600w,
/static/a9490b976c9b58670dc29c8e906c3cd8/1aa16/memory-dashboard.webp 1200w,
/static/a9490b976c9b58670dc29c8e906c3cd8/ef58e/memory-dashboard.webp 1206w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/a9490b976c9b58670dc29c8e906c3cd8/37b9b/memory-dashboard.png 300w,
/static/a9490b976c9b58670dc29c8e906c3cd8/ab3c7/memory-dashboard.png 600w,
/static/a9490b976c9b58670dc29c8e906c3cd8/5c837/memory-dashboard.png 1200w,
/static/a9490b976c9b58670dc29c8e906c3cd8/f7126/memory-dashboard.png 1206w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/a9490b976c9b58670dc29c8e906c3cd8/5c837/memory-dashboard.png&quot;
            alt=&quot;MCP Memory Service dashboard showing memories organized by tags&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;MCP Memory Service dashboard showing memories organized by tags&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;The real test: querying from a different client. Here’s Msty Studio (local chat client) consuming the same memory:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1fdcbf22f610262785801c0726ddb18f/8d8f5/msty-memory-query.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACRklEQVR42mVSu3LUQBBUhG1Mga3HStpdaSWt3nfn89nYPkiICagi4Q+ckBIYJxQJCYf55mZmdeeyj6Brn9Mz3TPeShnMS4NBGhSmRJaVMHmNoewxKzvkpoIpKuR5hVQo6FghEolDGCV0p6HTDFJlOPFjeJIeFBFVTYf5fImzsxW6fnQkpqpRVc0WNYrS0r1FWU4oCsaU0NoWb/wA3vzEd4R9M7ggfmCCKah2QbzuiO1jginJ7s7Qn3U9wvsaKrzWOVrboKEqGXXdToSVRU33WWYQRTEEqXmKOE6RJJIkaxyFAR7Wn+HdKYsDEaOiKheLJcleEGnrMAwzjIRd5dby2rgkvFfkW0KkOlF4GQv87j/AuzedIyzzAherS/JwiTRVkJLM1pkL4j2v+5BSbStUOGTC4i28W21xHCeQ9LBev8fV1TUysiAkiSwz3CJ6cp72U6dZuhQpDqIIm+IC3rtIwk9S5OTTzc3aEY7jzPnI6LqB0KMlb9u22/pM8skGfuemdLSeGoONXcFbE7tPPvAMjuMcs9mCZJ9juTzHYn7myJm0bXtH2NNIsYcsWZMShqFijiOBzcdPNDZBBEEe5FqRVPXoS5pO2O+sAxUQPwGTHx6/wubuJ7zWDyFIspEhSi3owxQ0eUQByfNgTrYPbtqLwyP8+fFAHhYNNE365eU1SRqdJzz9TV2jKfUkqSjdLGbaOAUyfQ4ebJ+a8vfbLyKsBpwKAVuTLzQmCX1gSUmaUPYQQSioUu6ucPCD8D/EVGUQBPhy+x3/AFm2Z/+malb4AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/1fdcbf22f610262785801c0726ddb18f/577d7/msty-memory-query.webp 300w,
/static/1fdcbf22f610262785801c0726ddb18f/93ccb/msty-memory-query.webp 600w,
/static/1fdcbf22f610262785801c0726ddb18f/1aa16/msty-memory-query.webp 1200w,
/static/1fdcbf22f610262785801c0726ddb18f/de8ae/msty-memory-query.webp 1487w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/1fdcbf22f610262785801c0726ddb18f/37b9b/msty-memory-query.png 300w,
/static/1fdcbf22f610262785801c0726ddb18f/ab3c7/msty-memory-query.png 600w,
/static/1fdcbf22f610262785801c0726ddb18f/5c837/msty-memory-query.png 1200w,
/static/1fdcbf22f610262785801c0726ddb18f/8d8f5/msty-memory-query.png 1487w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/1fdcbf22f610262785801c0726ddb18f/5c837/msty-memory-query.png&quot;
            alt=&quot;Msty Studio querying memory service&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Msty Studio querying memory service&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;Same memory, different clients. That’s the portability I was after.&lt;/p&gt;
&lt;h2&gt;Hosted Alternatives&lt;/h2&gt;
&lt;p&gt;Two other projects worth mentioning:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.letta.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Letta&lt;/a&gt;&lt;/strong&gt;: Developer platform for stateful AI agents with persistent memory. Their API lets you build agents that remember and improve over time. Works with OpenAI, Anthropic, Gemini. Hosted solution with memory built into the agent layer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.memsync.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MemSync&lt;/a&gt;&lt;/strong&gt;: Portable memory platform focused on cross-application context. Handles semantic and episodic memories, smart categorization, can import data from ChatGPT, social media, documents. This has probably the biggest feature set.&lt;/p&gt;
&lt;p&gt;Both are integrated solutions where you use their infrastructure. MCP Memory Service is more DIY: hooks, local storage, your own vector DB. Different tradeoffs.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Memory is really important for these systems. And perhaps we’ll go in a very different direction in the near future (&lt;a href=&quot;https://arxiv.org/html/2502.21030v1&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;as some papers already hint&lt;/a&gt;), but I really don’t like the idea of having something as powerful as an LLM owning all that information about me.&lt;/p&gt;
&lt;p&gt;Social media companies already know a lot about us, but my interaction with Meta or X is usually defined by the content (and ads) they serve. With LLMs, it is literally a chat box. The information the model owns could potentially steer interactions in ways that would feel very uncomfortable without explicit control.&lt;/p&gt;
&lt;p&gt;That’s why I think it’s so important, now more than ever, that we own the data. That’s real LLM portability. The memory stays with you, not some company’s servers.&lt;/p&gt;
&lt;p&gt;As models become commodities, this might become a big deal.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[When Knowing How to Code Is Not Enough]]></title><description><![CDATA[Disney’s 1940s cautionary tale on how NOT to instruct an agent. For a lot of people AI assisted coding is basically fancy autocomplete…]]></description><link>https://iurysouza.dev/agentic-coding-and-context-engineering/</link><guid isPermaLink="false">https://iurysouza.dev/agentic-coding-and-context-engineering/</guid><pubDate>Sun, 14 Dec 2025 12:55:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://youtu.be/snB8u_G3jVI?si=3XKiv2mtYDjCtEDP&amp;#x26;t=90&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Disney’s 1940s cautionary tale on how NOT to instruct an agent.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For a lot of people AI assisted coding is basically fancy autocomplete. Understandable. Copilot was some point the biggest AI product ever. Most devs had their first exposure to AI-assisted coding with that. And we all know that’s not that incredible as a product. You’re saving a lot of keystrokes, that’s for sure. But it doesn’t fundamentally move the needle.&lt;/p&gt;
&lt;p&gt;Then, at some point circa 2024 people started talking about coding agents and maybe you tried it again. “Ok… that’s cute, I guess”.
But you could still argue that it removes the “craft” from coding, or that it could stil not handle the complexity of a bigger codebase. That’s also a fair take.&lt;/p&gt;
&lt;p&gt;Nonetheless, I’m here to tell you that if you think agentic coding isn’t happening, it is likely because you have not seen what is currently possible or you lack a bit of imagination.&lt;/p&gt;
&lt;h3&gt;The engineer’s actual job&lt;/h3&gt;
&lt;p&gt;We need to be honest about why we are on the payroll. You are not hired type code. You’re hired to contribute to the bottom line using technical skills. Software is simply a means to an end.&lt;/p&gt;
&lt;p&gt;I mean, I know that can sound awfully dark and sad from an IC POV. I have to say that I got into this because I love coding. I like looking at it. I’ve spent countless hours writing the same thing in different ways and deciding what feels best. What looks more elegant as a solution. The good old &lt;a href=&quot;https://movie-sounds.org/sci-fi-movie-samples/quotes-with-sound-clips-from-the-matrix/me-and-my-brother-dozer-we-are-both-one-hundred-percent-pure-old-fashion-homegrown-humans-born-free-right-here-in-the-real-world&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;100% pure, old-fashioned, &lt;del&gt;homegrown human&lt;/del&gt;&lt;/a&gt; handcrafted code.&lt;/p&gt;
&lt;p&gt;But I also have two eyes. And I’d wager that if you’ve given cursor or claude-code (these are the most popular) an honest try by now (late 2025), you’re convinced they’re good enough for a LOT of things. And that set of things is not decreasing.&lt;/p&gt;
&lt;h3&gt;Gatekeeping, Changes and Pragmatism&lt;/h3&gt;
&lt;p&gt;In my first semester at Uni I learned basic C. Mallocs, pointer arithmetic, manual memory management. Next semester: Java and Python. Oh, you don’t need to do that anymore?&lt;/p&gt;
&lt;p&gt;But then, the online forums were full of people saying that garbage collection created freezes and lead to memory leaks and that software was getting slow and bloated. Real programmers managed their own memory.
But we know how that played out. Eventually, CPU cycles became cheaper than developer hours and soon enough we had enough CPU to spare.&lt;/p&gt;
&lt;p&gt;I’m not here to tell you we’re losing our jobs or that hand written code is going away, but we need to be aware of what’s up.
If you can contribute to the bototm line without manually writing every line, you’re still doing your job. In fact, in some cases you might be doing it better.&lt;/p&gt;
&lt;p&gt;The bottom line is that this shift opens up massive opportunities. Look at some of the internal tools at your company. They are probably terrible. You know that. Think about the friction caused by bad internal software or the recurrent tasks that eat up your week.&lt;/p&gt;
&lt;p&gt;Previously, you might not have justified the time to build a tool to fix these flows. With agents, the cost of writing software for these “small” problems drops to near zero. You can improve workflows and automate the mundane work that gets in the way of the interesting engineering problems. And in some cases it will let start coding in a higher abstraction level.&lt;/p&gt;
&lt;p&gt;There are places where you don’t want that and I could make an argument for where that’s the case. This is not the point of this article though, so I won’t go there.&lt;/p&gt;
&lt;h3&gt;Context engineering&lt;/h3&gt;
&lt;p&gt;I know a lot of good programmers with years of experience who are still unsure how LLMs can actually build useful code.&lt;/p&gt;
&lt;p&gt;The thing is that being a great coder does not automatically make you good at agentic coding.&lt;/p&gt;
&lt;p&gt;Getting results from an agent requires a different set of muscles. Some people call this context engineering. It is a mix of technical understanding and being able to codify that into the LLMs workflow. You have to take the mental model inside your brain when approaching a specific project and put it onto &lt;del&gt;paper&lt;/del&gt; markdown effectively (and timely). And for that you need to be aware of the constraints and of some of its internals.&lt;/p&gt;
&lt;p&gt;You cannot just let an agent loose on a repo and hope for the best. You need to build guardrails. Ideally, you have to instrument the agent for your specific codebase and your specific workflows.&lt;/p&gt;
&lt;p&gt;Tools like Claude Code work well because they have heavy instrumentation built on top of it. It’s more than just the model. So to get the most out of an LLM in a codebase, you often need to build your own harness on top of that foundation. You need to customize how it interacts with your environment to mitigate damage and ensure it behaves as you expect it.
Things like &lt;code class=&quot;language-text&quot;&gt;commands&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;skills&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;rules&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;subagents&lt;/code&gt; and concious context window management make a huge difference in the quality of results.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a71629d74c9f5217d6d806de77f71b7a/12462/claude-code-context.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.666666666666664%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACZUlEQVR42hWSyU/iYADF++d6nJjM2YzJTMycJk4yatQggzqGrSxdKNBSCl2+fqUtXaC0lKWAbBVBURO9DCa/w3vv9pIfMnfo9RA83+svM2Nos12N6mpFW8obQrYFMFvKtQFmiZ95t3tacXOvv86N7VR76NWRpcdNXSEcm4YhKiJla9WJr7jNSkMgGgKpimRTpnZ1PlRfws7bylsOoANxV8EDo4CEPUGHDACVg++HX/f3fv860hXasQRDLnH5Ky4XaXDZ13X//WmwffDmI1NXShk0mo6fOnIGCfvSNLDWSxfL3v69OmUZ7DH027DYoGIVOksVUiB3PjCr79tAhTRdRDE8Ec8lo9cnTRFFJg5nAeJ5qq/7/KRVXrrspluByWM6c5Viynflsli8I/58I4hUjSMmvUbP4SGfk7l0V8WRoF3LotHnRXsx1LomN+wIga9IWKxwcliOn+VuTqnLI/LiZ1OvixIN+IIsUlj+Jpu+dFTy83PgCG/r3nPY2cxs11FqlXxDolQu08TOIwd7sR/7jkb7Ld7UWJGnKCrN0mmRQ9sKjjwO5fd19+NlbIsoef6lWb4YaHitcN0REhbEYSkKyAhk4gz5r1ZMvK68iQ9sSLYg7msEshpITzPrYztyVIq//jKqHS/diquV5j7om4wlE5lE5C55oUFy3GafFu352DRhyZCIe6eKhH497IO3VXfRl0HptoLFTDG3wwaYqxZ8vSAy8VT8zJbzC68ajvTZUPOajMbnR1YZmXfonSvrcWMz1ly1KDFJwKI7LEjNetDb2VLLQhaF1Uzg1FeBGtiMwaM6lxqZ1H/odwPYweQ1kAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/a71629d74c9f5217d6d806de77f71b7a/577d7/claude-code-context.webp 300w,
/static/a71629d74c9f5217d6d806de77f71b7a/93ccb/claude-code-context.webp 600w,
/static/a71629d74c9f5217d6d806de77f71b7a/1aa16/claude-code-context.webp 1200w,
/static/a71629d74c9f5217d6d806de77f71b7a/c9db1/claude-code-context.webp 1408w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/a71629d74c9f5217d6d806de77f71b7a/37b9b/claude-code-context.png 300w,
/static/a71629d74c9f5217d6d806de77f71b7a/ab3c7/claude-code-context.png 600w,
/static/a71629d74c9f5217d6d806de77f71b7a/5c837/claude-code-context.png 1200w,
/static/a71629d74c9f5217d6d806de77f71b7a/12462/claude-code-context.png 1408w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/a71629d74c9f5217d6d806de77f71b7a/5c837/claude-code-context.png&quot;
            alt=&quot;Claude Code orchestration and context flow diagram&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Claude Code orchestration and context flow diagram&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Side Note&lt;/strong&gt;: I’ve put this together after dumping some of my notes into &lt;code class=&quot;language-text&quot;&gt;gemini-3-pro-image&lt;/code&gt;, aka Nano Banana and playing around with prompts. I find it amazing how much info it can compress in a single image like that.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The problem is that this &lt;strong&gt;requires actual effort&lt;/strong&gt;. It’s not free. Coding, as you may know, is something very personal. You pick 10 devs and look at how they code and you’ll likely find 10 different workflows. And it’s the same thing with agentic coding. You’ll likely have to build your workflow preferences into the agent before it you start trusting it, and that will require a LOT of trial and error.&lt;/p&gt;
&lt;h3&gt;Getting on the train&lt;/h3&gt;
&lt;p&gt;We are still in the phase where we are figuring this out as we go. There are no standard textbooks on context engineering yet. The industry is still settling on the patterns and it seems every other week someone comes up with a different .md file you have to manage.&lt;/p&gt;
&lt;p&gt;But I recommend you gradually start incorporating the new agentic features I mentioned before as you learn how to use the agents. Build something and see how far you get. Give it a go.&lt;/p&gt;
&lt;p&gt;Because, to me, the direction is clear. The ability to instrument agents and build high-quality context is just as important, in this new way of coding, as knowing the syntax of the language itself.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Where to go from here?&lt;/h3&gt;
&lt;p&gt;I find that you can learn a lot by understanding the mental model of the people that actually build the agent harness, then you can distill some of those ideas into your workflow. Some of interesting links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://rlancemartin.github.io/2025/10/15/manus/#&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;📖 Context Engineering in Manus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rlancemartin.github.io/2025/12/01/claude_diary/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;📖 Claude Diary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=xNcEgqzlPqs&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;▶ Al Agents That Actually Work: The Pattern Anthropic Just Revealed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=i0P56Pm1Q3U&amp;#x26;list=WL&amp;#x26;index=69&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;▶ I Reverse-Engineered Claude Code: Learn These Agent Tricks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6_BcCthVvb8&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Context Engineering for AI Agents with LangChain and Manus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=CQmI4XKTa0U&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;🎙 How AI Will Change Software Engineering – With Martin Fowler&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;If you approach this topic like a curious engineer approaches any other tech I think you may find out that there’s a LOT of engineering involved in getting the best out of it. This &lt;strong&gt;non deterministic&lt;/strong&gt; coding is definetly challenging and has many quirks, but can also be incredibly fun to do when you looked at from the right angle.&lt;/p&gt;
&lt;p&gt;I’ll share some of personal workflows in another article. Cheers!&lt;/p&gt;
&lt;hr&gt;
&lt;small&gt;
&lt;p&gt;&lt;strong&gt;Cover image:&lt;/strong&gt; &lt;em&gt;“Spellbound”&lt;/em&gt; (Sorcerer’s Apprentice / Fantasia) by &lt;a href=&quot;https://www.instagram.com/denyseklette/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Denyse Klette&lt;/a&gt;&lt;/p&gt;
&lt;/small&gt;</content:encoded></item><item><title><![CDATA[AI Coding Is Just Coding Now]]></title><description><![CDATA[I’ve been avoiding writing about coding with LLMs. There’s just so much noise in the space that it’s easy to sound like you’re an AI…]]></description><link>https://iurysouza.dev/agentic-coding-is-just-coding-now/</link><guid isPermaLink="false">https://iurysouza.dev/agentic-coding-is-just-coding-now/</guid><pubDate>Tue, 09 Dec 2025 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve been avoiding writing about coding with LLMs. There’s just so much noise in the space that it’s easy to sound like you’re an AI maximalist (whatever that means). But I think that a few months ago we crossed a line which closes this debate.&lt;/p&gt;
&lt;p&gt;Things are really polarizing these days and I know some people who have put themselves on the AI=BAD corner since the start. And that makes me want to write this.&lt;/p&gt;
&lt;p&gt;We don’t say that we’re researching online for how to fix a bug. It’s just implied that you’ll use the internet for that. AI coding is heading the same direction. Having code written by AI in some capacity will be the defacto way of doing it.&lt;/p&gt;
&lt;h2&gt;Slowly and then suddenly&lt;/h2&gt;
&lt;p&gt;I’ve been experimenting with these tools since the faraway 2021 when you’d write a comment describing what you wanted and hope the model would fill in the function body. But in the last six months, the majority of my coding has been done with LLMs. Don’t get me wrong. I still manually write a lot of it. The thing is that when I count personal projects + code at work, my total output has increased by a LOT and that’s mostly due to AI.&lt;/p&gt;
&lt;p&gt;The biggest jump for me was Claude Code, about six months ago. I try to keep my finger on the pulse, because though I could see how these tools could be useful I also knew they weren’t good enough yet. Over the years I tried Cline, Aider, Cursor, Windsurf, Gemini CLI then finally Claude Code + Sonnet 3.7. That was it. Things started to click.&lt;/p&gt;
&lt;p&gt;With claude it was now the first time I could &lt;strong&gt;see how the agent’s tool calling&lt;/strong&gt; worked. This transparency changed everything. Instead of mindless prompt engineering loops, I could &lt;strong&gt;look at the agent’s thinking traces&lt;/strong&gt;, understand what went wrong when it failed, and actually optimize my workflow rather than trying to force a square peg into a round hole. Alongside that, the new models have unlocked a really compelling and useful workflow that at this point is completely pointless to argue against.&lt;/p&gt;
&lt;h2&gt;The point&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0267fbb47bba79f3ac18b1e2b86db0f8/abbfe/rogers-curve-diffusion-of-innovation.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 70.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAB3UlEQVR42pWS224SURSG0UeoTUzTl/AJfINe9EZfgSb2vpe910o4lECRszpoh3OgmtjSSYRIyKSItAg0UyAgmd1hZs/QNsxwGBdOA5iAtivrYmWv/a3/nzVbpy4OJHVYzM1tMQzDcZxu9uhnrUomI8GD6H4iTET3faFA6DgB9Yd4SDs5b15oN1utFs/zf8G507zRaTH5bDt7xpc2g8lttQVc1neO1w6TxW83vDGflH/M3p/CgiDAMEVR1DvHLQxMOp32eDwIIRjhdDpzuZzWommaeE9IkrQQHg6H9Xq9UqmIogj1rb6Cr5XeaDQa9Af/UtYCeIwx3JZxXf28PvKvbATdr1KJSwn/BwZNiqIymUxHxDfEE9X+QPU/1pOe5/7drVhA7s/ZhQ5M9v4EWAV7SBTsUeLSsjyGfSsa/IJ0nzWYDhby+Xw2m50sVQc+C4VCqVRiWfai3XR/iRhJb2f3kWp7CLb1pOuZ17wZ9n2vVSE5LMyxDcowJfbtyJ78CDBnWRorv13dCHpBWYPp6hlkDbUnnzCGu90uQvDa+K+ndDybimUOr2JravQp7GyHSm5/ChuoA6bdrPxqaHnearCYnyqDbVgyFMViUZZlKMrlstaaFOPXjhAYnP+r7hu/AeEQ4wI7ZqwHAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/0267fbb47bba79f3ac18b1e2b86db0f8/577d7/rogers-curve-diffusion-of-innovation.webp 300w,
/static/0267fbb47bba79f3ac18b1e2b86db0f8/93ccb/rogers-curve-diffusion-of-innovation.webp 600w,
/static/0267fbb47bba79f3ac18b1e2b86db0f8/d757a/rogers-curve-diffusion-of-innovation.webp 1024w&quot;
              sizes=&quot;(max-width: 1024px) 100vw, 1024px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/0267fbb47bba79f3ac18b1e2b86db0f8/37b9b/rogers-curve-diffusion-of-innovation.png 300w,
/static/0267fbb47bba79f3ac18b1e2b86db0f8/ab3c7/rogers-curve-diffusion-of-innovation.png 600w,
/static/0267fbb47bba79f3ac18b1e2b86db0f8/abbfe/rogers-curve-diffusion-of-innovation.png 1024w&quot;
            sizes=&quot;(max-width: 1024px) 100vw, 1024px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/0267fbb47bba79f3ac18b1e2b86db0f8/abbfe/rogers-curve-diffusion-of-innovation.png&quot;
            alt=&quot;Rogers Innovation Adoption Curve&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Rogers Innovation Adoption Curve&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;I have a lot of thoughts on agentic coding that I’ll write about eventually. But one thing is clear: it’s not going anywhere. It’s not a fad. The way we use them is definitely still going to change, but when you cut through the noise from some people in the industry, and you give this an honest try you realize that this is just another way of coding and it’s here to stay.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Shadow Claude: A Second Opinion While You Code]]></title><description><![CDATA[What if you’ve had an LLM agent looking at your chat with Claude Code and calling bullshit on some of your bad decisions or maybe pointing…]]></description><link>https://iurysouza.dev/shadow-claude/</link><guid isPermaLink="false">https://iurysouza.dev/shadow-claude/</guid><pubDate>Sun, 30 Nov 2025 21:46:37 GMT</pubDate><content:encoded>&lt;p&gt;What if you’ve had an LLM agent looking at your chat with Claude Code and calling bullshit on some of your bad decisions or maybe pointing out tradeoffs that you’re not considering?&lt;/p&gt;
&lt;h2&gt;Wait what?&lt;/h2&gt;
&lt;p&gt;When you’re deep in a coding session with Claude, you’re in the flow. You’re making decisions, accepting suggestions, moving forward. But sometimes you should step back and ask things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is this the right approach?&lt;/li&gt;
&lt;li&gt;What am I missing?&lt;/li&gt;
&lt;li&gt;Are there gotchas I should know about?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The thing is, being self aware is hard. Stopping to ask these questions breaks your flow or you can get some decision fattigue and honestly, you just forget to do it.&lt;/p&gt;
&lt;h2&gt;What I Tried Before&lt;/h2&gt;
&lt;p&gt;I’ve experimented with some workarounds:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tmux splits with forked conversations.&lt;/strong&gt; I’d resume the same Claude session in a second pane and ask meta-questions there. “What do you think about this direction?” “Let’s think out of the box here.” It worked, but:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You’re stuck with the same bloated context&lt;/li&gt;
&lt;li&gt;You have to remember to actually do it&lt;/li&gt;
&lt;li&gt;It’s just one more thing to manage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Sub-agent patterns.&lt;/strong&gt; There’s an MCP called Zen that lets your agent spin up other LLMs, send them context, get feedback. Similar idea, but it’s the agent asking, not me getting passive insight.&lt;/p&gt;
&lt;p&gt;Neither was quite right.&lt;/p&gt;
&lt;h2&gt;The Click 💡&lt;/h2&gt;
&lt;p&gt;Then I saw that Msty Studio just &lt;a href=&quot;https://msty.ai/blog/shadow-persona&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;released&lt;/a&gt; a feature called &lt;strong&gt;Shadow Persona&lt;/strong&gt;. It’s essentially a side panel where a pre-configured agent reviews your main conversation and gives feedback.&lt;/p&gt;
&lt;p&gt;That was the click. This is a real thing people are building. Now I wanted the same, but for claude.&lt;/p&gt;
&lt;p&gt;So I did. At just before sleep. In bed. As one does.&lt;/p&gt;
&lt;h2&gt;Solution: Shadow Persona&lt;/h2&gt;
&lt;p&gt;A side-cart style agent that passively monitors the primary conversation and gives feedback without disrupting flow.&lt;/p&gt;
&lt;h2&gt;Version One: Validating the idea&lt;/h2&gt;
&lt;p&gt;The first prototype was simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tmux split&lt;/li&gt;
&lt;li&gt;Send a prompt to Claude programmatically via &lt;code class=&quot;language-text&quot;&gt;claude --print&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Dump the response into the side pane with &lt;code class=&quot;language-text&quot;&gt;tmux send-keys&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No formatting, no syntax highlighting. Just text. But it worked.&lt;/p&gt;
&lt;h2&gt;Version Two: Actually usable&lt;/h2&gt;
&lt;p&gt;This is what I built the next morning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Design Constraints&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero friction&lt;/strong&gt;: No keystrokes required once activated&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model independence&lt;/strong&gt;: Shadow LLM can differ from primary (e.g., Claude primary, Gemini shadow)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persona specialization&lt;/strong&gt;: Static system prompts define critique style&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context limits&lt;/strong&gt;: Aggressive summarization before transmission&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Workflow:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I’m in a claude-code session&lt;/li&gt;
&lt;li&gt;I type &lt;code class=&quot;language-text&quot;&gt;!shadow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A vertical Tmux split opens&lt;/li&gt;
&lt;li&gt;The split runs Neovim rendering a markdown buffer&lt;/li&gt;
&lt;li&gt;Every three conversation rounds, it triggers automatically&lt;/li&gt;
&lt;li&gt;A separate LLM (configurable) evaluates the conversation using a predefined persona prompt&lt;/li&gt;
&lt;li&gt;The response renders live with full markdown syntax highlighting&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result: I’m working on the left, and on the right I’m getting ongoing commentary.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;“This approach has these trade-offs…”&lt;/li&gt;
&lt;li&gt;“You might want to consider…”&lt;/li&gt;
&lt;li&gt;“Watch out for this edge case…”&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;A second opinion, automatically delivered, without breaking flow.&lt;/p&gt;
&lt;h2&gt;The Recipe&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tmux&lt;/strong&gt; for pane management&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Neovim&lt;/strong&gt; for rendering markdown with syntax highlighting on the terminal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An LLM provider wrapper&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persona prompts&lt;/strong&gt; that define what kind of feedback you want&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;How It Actually Works&lt;/h2&gt;
&lt;p&gt;Here’s what’s happening under the hood:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;┌───────────────────────────────────────────────────────┐
│              Claude Code Session (tmux)               │
│                                                       │
│ UserPrompt ──► trigger.py ──► FIFO ──► loop ──► LLM   │
│                                                  │    │
│ ┌────────────────┬───────────────────────────────▼──┐ │
│ │  Main Pane     │  shadow-claude Pane (nvim)       │ │
│ │                │                                  │ │
│ │  claude code   │  nvim --listen .../observer.sock │ │
│ │                │        │                         │ │
│ │                │  observer.lua (buffer mgmt)      │ │
│ │                │        │                         │ │
│ │                │  render-markdown.nvim            │ │
│ └────────────────┴──────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The flow is &lt;em&gt;kinda&lt;/em&gt; simple: Claude Code fires a &lt;a href=&quot;https://code.claude.com/docs/en/hooks&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;hook&lt;/a&gt; when you submit a prompt. That hook triggers a script that writes to a named pipe. Another script runs a loop in the background that reads the pipe, it eventually hits the gateway, and streams the response into a Neovim buffer on the right.&lt;/p&gt;
&lt;p&gt;When you end the session, a different hook kicks in and generates a summary.&lt;/p&gt;
&lt;h2&gt;The Moving Parts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Hooks&lt;/strong&gt; are how Claude Code lets you intercept events. I use two:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;trigger.py&lt;/code&gt; fires on every prompt, counts rounds, decides when to ping the shadow&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;summary.py&lt;/code&gt; fires on stop, sends the transcript off for a summary&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The FIFO pipe&lt;/strong&gt; is just old-school Unix IPC. The hook writes JSON, the loop reads it. No sockets, no complexity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The gateway&lt;/strong&gt; is a thin wrapper around LLM providers (Anthropic, Google, whatever). It handles caching, loads the persona prompt, streams the response back.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The observer buffer&lt;/strong&gt; is a scratch buffer in Neovim. A small Lua plugin manages it—append new content, clear when needed. &lt;code class=&quot;language-text&quot;&gt;render-markdown.nvim&lt;/code&gt; makes it look nice.&lt;/p&gt;
&lt;h2&gt;State I Have to Track&lt;/h2&gt;
&lt;p&gt;A few things live in &lt;code class=&quot;language-text&quot;&gt;~/.cache/shadow-claude/&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Counter&lt;/strong&gt; — how many prompts since last shadow trigger&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Last position&lt;/strong&gt; — where I left off reading the transcript (so I don’t re-send the whole thing every time)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Events log&lt;/strong&gt; — mostly for debugging&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nvim socket&lt;/strong&gt; — so the loop can talk to the observer buffer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This works surprisingly well and Nano Banana Pro visualized its architecture like this:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fc65acd6ce6e82c69fa314bda4ba2ce6/18649/shadow-claude.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.666666666666664%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAC4jAAAuIwF4pT92AAACb0lEQVR42h2Sy2vUUBjFsxc3UlzpQhDrQpQu3Pk/iODWhdiVj4KKr6KbFlrBLqpgUWofaEHoOLZioaUPhJmOncyj02ln4kyeN4+be3OTO8lNJhnrQoyFs/g2v8PhO4dDmCLcQU7HcTrYoYQEjqrY0KZBXK8LP3cqfGlXUc0kOWQs9lk3PeL4MOom3bjHHcH0CPY9LyTIMwsbxVbe8RmERFEMWTGw0yEeE3VX1gk0IXa8lAyj5D+MHd/CgW4SQZAMFWglPltVqzIEmkGIz1gSRr2aoLZECyL/l6Jj7Ca9P1EUc8jxAYD6QUVttyWLGhbWyvxiDZV1Xzdsh/iUhgZm/IGcK7UAdA0SMBYR4h3ByKutZR7cvLow+8HePwBvR/HeblY1c20Q0MgjAbE9FThjmeWZteJ2SZN0x/V8RQUe9TnX8aZfDPWdOjn5/Jk+PlybmpAkc0m2CrIVkPDL3vqqnPtRb4x+zswvFSoFvYMPCzvVR0/G6vstThPB3OPBc6f75ifGVBGIBjbt8F1FKmhUUOzBT9NDX98v5LbSgKbkVnn5zt2HlwaunDl7ucjvcXy+NHfvxsjtW+1GiwS9tK3Nndbk92ZRRJsN+vTj1nhmUTc8QwqS6O/Iy4njJ4719w9cuz4oSganASQ32xjR9AWpfb2pv8muLG9Xyk270TZME1HKgOrweRFa/tD94fMXLr56PQt0End/c+kcEGEQdSB0U1gBOLOSm8msl2sStF2PhulsXBq0JXNjozk1nf22mm+IMIqSkMVcWq9hEtMiFnRTpXsAmikIsqroyHbTqlIRN7AQlQBK07EwNg2LsW7gd/8B4SkaiAJo5JcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/fc65acd6ce6e82c69fa314bda4ba2ce6/577d7/shadow-claude.webp 300w,
/static/fc65acd6ce6e82c69fa314bda4ba2ce6/93ccb/shadow-claude.webp 600w,
/static/fc65acd6ce6e82c69fa314bda4ba2ce6/1aa16/shadow-claude.webp 1200w,
/static/fc65acd6ce6e82c69fa314bda4ba2ce6/daa23/shadow-claude.webp 1800w,
/static/fc65acd6ce6e82c69fa314bda4ba2ce6/b1e4f/shadow-claude.webp 2400w,
/static/fc65acd6ce6e82c69fa314bda4ba2ce6/68693/shadow-claude.webp 2752w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/fc65acd6ce6e82c69fa314bda4ba2ce6/37b9b/shadow-claude.png 300w,
/static/fc65acd6ce6e82c69fa314bda4ba2ce6/ab3c7/shadow-claude.png 600w,
/static/fc65acd6ce6e82c69fa314bda4ba2ce6/5c837/shadow-claude.png 1200w,
/static/fc65acd6ce6e82c69fa314bda4ba2ce6/2f4db/shadow-claude.png 1800w,
/static/fc65acd6ce6e82c69fa314bda4ba2ce6/84976/shadow-claude.png 2400w,
/static/fc65acd6ce6e82c69fa314bda4ba2ce6/18649/shadow-claude.png 2752w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/fc65acd6ce6e82c69fa314bda4ba2ce6/5c837/shadow-claude.png&quot;
            alt=&quot;Shadow Claude Architecture&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Shadow Claude Architecture&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;And here’s Shadow Claude giving feedback on its own implementation&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f76d9aa9eb6b77656b6ef6880acfd22a/d7271/shadow_claude_demo.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 63%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACq0lEQVR42j1TWXLbMBTLMeIlTSQ71i5Rorho32xnmaT9yLT3vwkKOZ1+YB5FSuADHnTnbjY47nfw93tkjoM2zjCnGv1zjv4gULsppBMifTwh+nHkey68/RPxCH/3CLE9IN8ekWwcuPdb3BXSoG5HNO2Etpvx+fmFX5+/oWSDJCyQRBJRVCD0JHTUoIwqpJ5G5JUQzwazO+F8WNA6DdwNCdNMoVIdWtWily3SWCFNNGReI2Nd10ms4fsKqmiQZxX37Q0pEVNNlBiEvNzd7HCXCQ1tJ9T1BePwCaN6KJKVRBKXN8SRQnhSsKKGiHiBXyINFBIi5UVpoBGfxHeHNaWOyxtsPSHPLYrMoBC8PTUIopKQCELJDg1syYvVhFKOkHKCLEZoM6PkXuAXcFbCOCnQ2R6v4wVdYTFR+mJ6DLpFmbI7PyMZEVp0zRt6wtgXmOoFtnplI2+ouzcEQQnnfoM7P8xh6E2nRuRegaVacGkumFj7soOQA07NB04eOy8uxBmleYHUV6RiRpKMCKMeB0f8IwwEtOaE21d6ZTGPH1iInp6K+QvPyx8EeQefk7XljIooCalY9Sp3ZgoqnI7Zt4dxInnQoesufLGB0T3aakTLPcnOA3rp+4KSLMbunbIp015hzAU111VzhVILQg7qRqhNh+nygXF+xTjRl3qgRz1EplHQQ8lYxRyMFxgO48q4rBI7RHGPRIxI8hEx6+GQf0subIeyGiAYHbFKYBaTVHG6DDOJMhKLtcu1Q3bTmjMq2lHXV2jKVWpNR38LvrO5JyGlqnqBZ96RNj8xVNcbkRcKrAPzghwhk2CHgUQvUPqMgvHJGZ11Lemnrs9o5wHulpLXdLvbBxz54G7v8bTdwN3tcPiPPf/1PbynBxxYb2fb9Zvdbe3yfN1/fny4efgXPKSa6ki66AkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/f76d9aa9eb6b77656b6ef6880acfd22a/577d7/shadow_claude_demo.webp 300w,
/static/f76d9aa9eb6b77656b6ef6880acfd22a/93ccb/shadow_claude_demo.webp 600w,
/static/f76d9aa9eb6b77656b6ef6880acfd22a/1aa16/shadow_claude_demo.webp 1200w,
/static/f76d9aa9eb6b77656b6ef6880acfd22a/daa23/shadow_claude_demo.webp 1800w,
/static/f76d9aa9eb6b77656b6ef6880acfd22a/b1e4f/shadow_claude_demo.webp 2400w,
/static/f76d9aa9eb6b77656b6ef6880acfd22a/68fe4/shadow_claude_demo.webp 3018w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/f76d9aa9eb6b77656b6ef6880acfd22a/37b9b/shadow_claude_demo.png 300w,
/static/f76d9aa9eb6b77656b6ef6880acfd22a/ab3c7/shadow_claude_demo.png 600w,
/static/f76d9aa9eb6b77656b6ef6880acfd22a/5c837/shadow_claude_demo.png 1200w,
/static/f76d9aa9eb6b77656b6ef6880acfd22a/2f4db/shadow_claude_demo.png 1800w,
/static/f76d9aa9eb6b77656b6ef6880acfd22a/84976/shadow_claude_demo.png 2400w,
/static/f76d9aa9eb6b77656b6ef6880acfd22a/d7271/shadow_claude_demo.png 3018w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/f76d9aa9eb6b77656b6ef6880acfd22a/5c837/shadow_claude_demo.png&quot;
            alt=&quot;Shadow Claude Demo&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Shadow Claude Demo&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;As you can see form its comments, this is not quite there yet, but I want to publish this once I’ve get to use it for longer and have refined it a bit more. It’s a fun little side project.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Running on the Edge: What, why and how]]></title><description><![CDATA[I recently gave a talk about Running LLMs locally on Android at the GDG Berlin Android, and I thought it would be useful to break it down…]]></description><link>https://iurysouza.dev/android-on-device-ai/</link><guid isPermaLink="false">https://iurysouza.dev/android-on-device-ai/</guid><pubDate>Sun, 06 Apr 2025 14:22:20 GMT</pubDate><content:encoded>&lt;br&gt;
&lt;p&gt;I recently gave a &lt;a href=&quot;https://www.youtube.com/watch?v=VvJ14jkNl1s&amp;#x26;list=WL&amp;#x26;index=3&amp;#x26;ab_channel=GDGBerlinAndroid&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;talk&lt;/a&gt; about Running LLMs locally on Android at the &lt;a href=&quot;https://www.meetup.com/berlindroid/events/305453597/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GDG Berlin Android&lt;/a&gt;, and I thought it would be useful to break it down into an article.
This article breaks down what ‘Running an LLM on the Edge’ means, why you’d consider it for Android apps, and explores the current options available.&lt;/p&gt;
&lt;h2&gt;Edge Inference&lt;/h2&gt;
&lt;p&gt;An LLM is basically a large file containing learned language patterns represented as numbers. Running an LLM is often called &lt;em&gt;inference&lt;/em&gt;. That just means we’re actively calculating and predicting the output based on its learned internal model. That is: performing statistical inference.&lt;/p&gt;
&lt;p&gt;To run an LLM your text prompt is converted into number tokens, fed into the model which calculates the most likely next token. This process repeats, using the newly generated token as part of the input for the next step, allowing the model to generate (hopefully coherent) text. That’s why they call them &lt;a href=&quot;https://en.wikipedia.org/wiki/Autoregressive_model&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;autoregressive models&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, when you run an LLM locally, you’re essentially using a program to perform these tokenization, calculation, and step-by-step prediction steps using that massive model file stored on your machine. It gets complex under the hood, but the core idea is just that: predict the next piece of text based on what came before.&lt;/p&gt;
&lt;p&gt;You can get a bit more of the intuition what that looks like when using the &lt;a href=&quot;https://huggingface.co/docs/transformers/en/index&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;transformers library&lt;/a&gt; to run &lt;a href=&quot;https://colab.research.google.com/github/google-ai-edge/mediapipe-samples/blob/main/codelabs/litert_inference/Gemma3_1b_fine_tune.ipynb#scrollTo=AM6rDABTXt2F&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Gemma3&lt;/a&gt;. If you find that confusing, don’t worry. We can go up another abstraction layer.&lt;/p&gt;
&lt;p&gt;Probably the most famous open source inference engine is the &lt;a href=&quot;https://github.com/ggml-org/llama.cpp&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;llama.cpp&lt;/a&gt;. There are alternatives, and even abstractions built on top of it like the &lt;a href=&quot;https://ollama.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ollama&lt;/a&gt; project, which makes running an LLM as easy as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ ollama run gemma3:27b
&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; Send a message &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;/? &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So that’s &lt;em&gt;Inference&lt;/em&gt;. What about &lt;em&gt;Edge&lt;/em&gt;? Well, that’s just means the consumer hardware. Your laptop or your phone.&lt;/p&gt;
&lt;p&gt;Ok, with this new knowledge we can reinterpret the article’s title. It’s about what are the alternatives for &lt;strong&gt;Inference Engines on Android&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;But y tho?&lt;/h2&gt;
&lt;h4&gt;Weren’t LLMs Super Hardware Intensive?&lt;/h4&gt;
&lt;p&gt;That’s one of the questions that can pop up. We hear about LLMs needing data centers full of GPUs, so running them on a phone sounds… ambitious. Plus, we already have powerful options like ChatGPT and Gemini available in the cloud. Why go through the hassle of running them locally?&lt;/p&gt;
&lt;p&gt;Well, there are some reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cost:&lt;/strong&gt; Cloud LLM APIs often charge per token (input and output). If your feature gets popular, those costs can skyrocket. Running on-device means the inference cost is essentially offloaded to the user’s hardware (the edge), potentially saving you a lot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency:&lt;/strong&gt; Calling a cloud API involves network roundtrips. On-device inference &lt;em&gt;can&lt;/em&gt; be significantly faster, leading to a much snappier user experience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Privacy:&lt;/strong&gt; This is a big one. If your app deals with sensitive user data (think personal messages, health info, finances), sending it to a third-party cloud might be a non-starter. On-device processing keeps that data on the user’s phone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Offline Availability:&lt;/strong&gt; Self explanatory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Buried in the AI News Hype Cycle:&lt;/h2&gt;
&lt;p&gt;When we talk about AI breakthroughs, the news usually focuses on the massive, state-of-the-art (SOTA) or &lt;em&gt;frontier&lt;/em&gt; models. These are the Geminis, Claudes, Deepseeks and GPT-4s of the world. They crush benchmarks every week and dominate the headlines. But they’re just the tip of the iceberg.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1198px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/70c3dc92a3b792b9e47adea9c268c1af/14c1b/models-iceberg.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 56.666666666666664%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACmklEQVR42o2Q20uTcRjHX6I8BRIEK/Gwdx2oJBMiL6yLSBSU8FDgNiyUiLQphWYpVitFyVJUmGvvPJSS2k6vTOpC7XCRmYGIUKjbnDu7g3Nq0R/w7ZlK1I108eH5vS/P83kOjNITQIs3iEZ/CHLinn8V1ctB3LZ5ULHkRgXFciL8XefwosntQxvVKKmmxxfCq8A69KsbeBP6ifG1X2Dq3X5UL1hx88s0rn2aQt3kFIp0wyicX4LE6YfY4oR40QkpUWJ1QUbiO45lPHT60OwOQEHNX/jXoFnZwEjoB5gyuxeS2Tlkcz1o7++D5dsMGjVapOuNyJycRsaiC1kmG3JMduSbHZBQg2L6V0rTV9mX8cjlR6snCDVNO7SyDkZsdiL9uxVynsfoCI/PEx/RrFDgxP0GnPo6i9QFG87MWXFu3ooLc0vIockL5m0QmxwoJrmMxDUkbiLxczoDk0lJGSR9GwiibGwCWd2vkd7Zh4QnKggmZsDSmkfNNqRY7Egz23GepswmWZ6JTmFxodjqgczuQ60rgGfeVTDHDWNI7ucRd7cBu8rrEFP5GDG3HiBWVgtBaxeSjB8g1I9CRHlHiGOGUSRTPGkYRwqRSpzmx5E2/A5nje/BCDktBPUd2H2xENH5UkTlShAZpqAI8U85iFQasIoBsMpBCDsHkUQxcZsEIq5zAIL2lxB09OEAvRm2Swu2R4995TWIyJUi+nIJIvOuYG9RKYRKEnXrwXbp/kH0F6xai0TV0CZhF5OkHoKwV4eEtl7ESK8j6tJVmlCK/ZVyiHp5sJxms2hHNhttvRmRmoeI05HUAIG8BXto9QhaPa5J8R9CDRJpzTDC7TzmEGfEYW4Y8bTeQbpZ7I0qup3qT8LOaLZuGRaqt4S/AalKRJKth1o5AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/70c3dc92a3b792b9e47adea9c268c1af/577d7/models-iceberg.webp 300w,
/static/70c3dc92a3b792b9e47adea9c268c1af/93ccb/models-iceberg.webp 600w,
/static/70c3dc92a3b792b9e47adea9c268c1af/0f5c3/models-iceberg.webp 1198w&quot; sizes=&quot;(max-width: 1198px) 100vw, 1198px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/70c3dc92a3b792b9e47adea9c268c1af/37b9b/models-iceberg.png 300w,
/static/70c3dc92a3b792b9e47adea9c268c1af/ab3c7/models-iceberg.png 600w,
/static/70c3dc92a3b792b9e47adea9c268c1af/14c1b/models-iceberg.png 1198w&quot; sizes=&quot;(max-width: 1198px) 100vw, 1198px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/70c3dc92a3b792b9e47adea9c268c1af/14c1b/models-iceberg.png&quot; alt=&quot;LLM iceberg with SOTA models&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;LLM iceberg with SOTA models&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;But beneath the surface, something else is happening.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1193px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/10e96ff7ca48cd1891894ed757991e7b/e4f7d/models-iceberg-2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 56.666666666666664%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACoklEQVR42oWR6UvUURSGf0REUhAVKqkz85uwiIqsKKJooShsStM2pyRFCFo0bZFI0xC1IrFM0nHUMaMyG8fc6EPRYlG0mEQQuY2OM8447rb9A0/HJqJP+eHhHjjvec9771VMniHy+4fJHRonSzg3MEaqd4STzn5OONwkOTwkCslCmtNLbt8AV2WmuH+EctHelpmakW80jf3g8fhPlGz3IGe7nCS+bSXh5RvOt7QSb6tj78cvGN1DxHS52G93YZQzvruPYz0eTsuyDNcAl6RfKMaWwXGqh7/SMPod5Xivl9jPHWwvrySnvJy2Tx+4Uf+ANdW1bH31ni1itK2jF4MQ1ekkptNFnL2PIz1uTvX2kynGeZ5hSgZGuSumykEZWN/mIMVWS5O1ig/vXmGusLDkTDphL9+yXExWtXWzrr2HzW09GEQb3e7A2OEkvquP45L4rDzFRUlb7B1FMbQ72SCNR9++k/m6lchbNjaV3EW9bGJucwuqiEPtTpZ2u1gtbJJ0Brub6C43RruHBIeXJNcQ6fIP+XJ1ZXH9E5bcf4iamceU5Av4peYyJzWb4OQMQnIK0DQ1oxONvuEpocIiYakQ1vCM5cJKYXXjc9aKbuPDFygas5UFuYWo4ZEsjIgmIHwXQYZoloXvZEXKOVRTNWqRJC6qQido/6Ap9hEkvYDrlQQUVBIotaKWWZlvqcE/OQ2/CCOz9sShRuzDf+c+ggpuorc8QC21opbV/EX/DxM9jamKEGGiVnSl99BWWAkpvIXfgcNM332IGVGxzD6aKma1qOb7PsP/8XuRr1bUUht6sw2dxUZg9nWmRcYwdYckPH8FfUXdpIZaSaaRq+pKqn2G8831hJobCTbdYV6+hZkJSQRmXUNnuufbPEk6reiCxVD7x/AXLThFujhExlUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/10e96ff7ca48cd1891894ed757991e7b/577d7/models-iceberg-2.webp 300w,
/static/10e96ff7ca48cd1891894ed757991e7b/93ccb/models-iceberg-2.webp 600w,
/static/10e96ff7ca48cd1891894ed757991e7b/92c78/models-iceberg-2.webp 1193w&quot; sizes=&quot;(max-width: 1193px) 100vw, 1193px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/10e96ff7ca48cd1891894ed757991e7b/37b9b/models-iceberg-2.png 300w,
/static/10e96ff7ca48cd1891894ed757991e7b/ab3c7/models-iceberg-2.png 600w,
/static/10e96ff7ca48cd1891894ed757991e7b/e4f7d/models-iceberg-2.png 1193w&quot; sizes=&quot;(max-width: 1193px) 100vw, 1193px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/10e96ff7ca48cd1891894ed757991e7b/e4f7d/models-iceberg-2.png&quot; alt=&quot;Efficient small models&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Efficient small models&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;There’s a fast growing category of smaller, highly &lt;em&gt;efficient&lt;/em&gt;, and often &lt;em&gt;open-source&lt;/em&gt; models. I like to call these the “Top Left Corner” models because when you plot performance against resource usage (GPU requirements or size), they show up on that top-left spot: high performance with low resource needs.&lt;/p&gt;
&lt;p&gt;These models are catching up &lt;em&gt;fast&lt;/em&gt;. Models like Mistral 24b, Llama 3.1, and Google’s own Gemma3 family are hitting benchmarks today that were SOTA just 6-12 months ago. Benchmarks show these smaller models achieving performance levels comparable to much larger models from just 6-12 months prior, often on significantly less demanding hardware.
Think about that for a second. Models that can run on consumer hardware right now are just as capable as huge models were just last year.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/9f183cc6dc75ac7f143e919d4fb50fe5/496bd/mistral-bench.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 52.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABEklEQVR42oWQ63KCMBCF8/6P1tqi1QSwVASkXFpxiCYQaJJuA8MwGbHfj52dyZ5zNos2LyuCdz4hx4/AWT3jjbNbv27XzqWqtNZKKb0Mkp+hCt/Y3rkdCD8QqPKc65aDUP8Hksm7OmKKny77bX0K6/P3gzTrCcnIU1+p5letpDXa931nWLJDktHR1QrR+sr5zSDln2/TdXVdU0rBdBTPXaETECTE3Zxeyh/DJLHFlDGIUrMfPjgBGlayrrKELYY1QA/fkAZohiHop2ZylGbzYXhMBtq2HZqmacqyTNO0KIqTIY7jPM+zLIuiSAgBymEYvBCfwRiDGoYhxjhJEt/3XdeF6nleEASEkKqqwJ2ZuwghfgE2Q34ZPI490gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/9f183cc6dc75ac7f143e919d4fb50fe5/577d7/mistral-bench.webp 300w,
/static/9f183cc6dc75ac7f143e919d4fb50fe5/93ccb/mistral-bench.webp 600w,
/static/9f183cc6dc75ac7f143e919d4fb50fe5/1aa16/mistral-bench.webp 1200w,
/static/9f183cc6dc75ac7f143e919d4fb50fe5/daa23/mistral-bench.webp 1800w,
/static/9f183cc6dc75ac7f143e919d4fb50fe5/f6269/mistral-bench.webp 1988w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/9f183cc6dc75ac7f143e919d4fb50fe5/37b9b/mistral-bench.png 300w,
/static/9f183cc6dc75ac7f143e919d4fb50fe5/ab3c7/mistral-bench.png 600w,
/static/9f183cc6dc75ac7f143e919d4fb50fe5/5c837/mistral-bench.png 1200w,
/static/9f183cc6dc75ac7f143e919d4fb50fe5/2f4db/mistral-bench.png 1800w,
/static/9f183cc6dc75ac7f143e919d4fb50fe5/496bd/mistral-bench.png 1988w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/9f183cc6dc75ac7f143e919d4fb50fe5/5c837/mistral-bench.png&quot; alt=&quot;Mistral 24b latency results&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Mistral 24b latency results&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/4939f442bd07dd41e1a36fed986d905c/d3430/gemma3-bench2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 70.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAABe0lEQVR42pWTWW7CMBCGvSReEhuyQANBqfpU1ANUbQ/Q+x/p74zDktIE1IdPOB7PPytCCIE5lFI4HA4YhiHR9z32+z26rsNut0OWZVjwFZBSQuscUuVQmjF01rDGwFpHWBg6T2GfRcFMS1SbI8LTB/GOpv+EseH0YN6RBRkWz/OcEtJTQYFV+0piX4gkaFx1V2wKV+C9/y2olUBsj6ifvxE2b7Cxw7odKLJ9KPiHPDep5Fi9wPgNhKSBaDv2VD7O0BclyjLQkPLxrmmalLJ3frFPZ5a2gbnYWYynWFCk/5TGa8N9YxRDotehkLEsR0GeGosXRYG2beGcS7Zz46UcHdnONuvGoRhjbwVDSpsfBTozVd2kyI7aEWNMjvz2QW9Fipyi0QpMxn+XqehNgFHQ+yJRUHk8MaXkpdlXxm+l9Al1ulNzPQzgFeLyWJiz5fsYV8nG9+t1hRBC+g4hpl+lstRzd92S+VU5rwQPhuH1qusa2+02/dWmpU5L/gFPdCc7JHwHjgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/4939f442bd07dd41e1a36fed986d905c/577d7/gemma3-bench2.webp 300w,
/static/4939f442bd07dd41e1a36fed986d905c/93ccb/gemma3-bench2.webp 600w,
/static/4939f442bd07dd41e1a36fed986d905c/1aa16/gemma3-bench2.webp 1200w,
/static/4939f442bd07dd41e1a36fed986d905c/245e5/gemma3-bench2.webp 1552w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/4939f442bd07dd41e1a36fed986d905c/37b9b/gemma3-bench2.png 300w,
/static/4939f442bd07dd41e1a36fed986d905c/ab3c7/gemma3-bench2.png 600w,
/static/4939f442bd07dd41e1a36fed986d905c/5c837/gemma3-bench2.png 1200w,
/static/4939f442bd07dd41e1a36fed986d905c/d3430/gemma3-bench2.png 1552w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/4939f442bd07dd41e1a36fed986d905c/5c837/gemma3-bench2.png&quot; alt=&quot;Gemma3 ELO Score vs Size&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Gemma3 ELO Score vs Size&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/01820748195b1c10fe4391c831e8b708/2ac2e/gemma-gpu.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 63.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB60lEQVR42o2TzW7TQBDHndixHUf22ok/YjskbpxWTtwvO7FaQBQh8Q5ceuAC4tpbT5wAqef2NTjSqq/AyyAE9z+zK1IclBAOo/2a/c3szH8lSZKwzhqNBnzfRxRFYgzDEEEQwPM8NJtNbLon1RetVmvFWdd1GIYhxna7LYzPebCtQEVpId3N4fkhZEVHx+z969J2oKrpKKvnGMQhxmfXKF99haKaMNq6yJpntSWzv4CqhuP5U8R9D7Pze5xc/oQb7uLwuILFunC6Hmy7t1Ljum0APiOgj/TtHQ4/fYf/KEM5f4Ke28c0L5FOZlRnFZbFHqB1+Fpg5HtYXNzi5c03BMM9FOUpuj1fAJOdPQyGYxwVj6k5Bu174l4dXgPqKBZnCEgi7z7c4+rzDwySDAVl6Hp95AcVdtIMw4SX4ZSyDlCdvIBLTWTMgeO4q0D+lP2DBTm6eP3mCu8/fkEUJ5jtF2B2F+PJlNYjhNEQ2fSIaupSkDlcAk/oLJ1kf4A8Xd5Jxizo1G1ZblIAmaQkw7Ftcc41qKqq0CpjJvnI1CQGldaapgm9CiB3SpKEDm3keY44jn9HaqDT6aCqKpimuSL20Wgk5hy0/AgPH4JH4nRFUah7lnBaXuZOHMp96ntLn7W6FM8h4DLaf4p3o/0CH4U0qxg0Y7QAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/01820748195b1c10fe4391c831e8b708/577d7/gemma-gpu.webp 300w,
/static/01820748195b1c10fe4391c831e8b708/93ccb/gemma-gpu.webp 600w,
/static/01820748195b1c10fe4391c831e8b708/1aa16/gemma-gpu.webp 1200w,
/static/01820748195b1c10fe4391c831e8b708/064f3/gemma-gpu.webp 1418w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/01820748195b1c10fe4391c831e8b708/37b9b/gemma-gpu.png 300w,
/static/01820748195b1c10fe4391c831e8b708/ab3c7/gemma-gpu.png 600w,
/static/01820748195b1c10fe4391c831e8b708/5c837/gemma-gpu.png 1200w,
/static/01820748195b1c10fe4391c831e8b708/2ac2e/gemma-gpu.png 1418w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/01820748195b1c10fe4391c831e8b708/5c837/gemma-gpu.png&quot; alt=&quot;Gemma3 ELO Score vs GPU requirments&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Gemma3 ELO Score vs GPU requirments&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1b44d4b22a10c70558bc71fded5e7c9a/42afc/gemma-bench-new.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 47.333333333333336%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABuElEQVR42jWS646bMBSE+Z8EYnM1GDDXhAWS3VTdStXuU1Tq+7/JdI5Rf1gHG/jOzBkHlXuDqe+ouw3D7YWCz6ZZcFIWF93grGoui/72gayaUbYLEjP6GhcD3PSE7VbU/YYoaRGofMAl6aDSngcOUTZB2SfXO1S5I+T5mWDTrLhmA/J6gcpHFM0bdDEhrW4QRjt9UMwiwJHA3n8UEnw1VND9Rux+QdcvqOqBa7nBdDuUAOwCbWYU7Yqkohu3+fN6fEfOpgeQKmLWKO0QFXevTurVUFW5epX/f8ypTGrpdg+XKns3v3wTbznUDmHWI4xb2pqg20+o+gfV0TZVRoX8SKBYFoVsbvudM+Xsh4ffi2V5F2iZIYE65fwIjLIZqvlEWD4R5XeqPkZiaVkTWNQyu9GDBOgISoyEtUJYgXQ9C5CBhB7IBN0LpmUwnKcEdokZSnuEktkjlIqKJZCKt0POO7khHEcQs9s5dsjTA1hUA/587fj7vaKxHU66ZViOFh9emYBjM/l9Zu9oGIbsxbI0ouWeKhxiCSRp/bW52J845RuVHTBZApJ5y9WQmYmytJxp/ekbdQxFbP8DDNLweSmBn8sAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/1b44d4b22a10c70558bc71fded5e7c9a/577d7/gemma-bench-new.webp 300w,
/static/1b44d4b22a10c70558bc71fded5e7c9a/93ccb/gemma-bench-new.webp 600w,
/static/1b44d4b22a10c70558bc71fded5e7c9a/1aa16/gemma-bench-new.webp 1200w,
/static/1b44d4b22a10c70558bc71fded5e7c9a/daa23/gemma-bench-new.webp 1800w,
/static/1b44d4b22a10c70558bc71fded5e7c9a/b1e4f/gemma-bench-new.webp 2400w,
/static/1b44d4b22a10c70558bc71fded5e7c9a/9422f/gemma-bench-new.webp 2522w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/1b44d4b22a10c70558bc71fded5e7c9a/37b9b/gemma-bench-new.png 300w,
/static/1b44d4b22a10c70558bc71fded5e7c9a/ab3c7/gemma-bench-new.png 600w,
/static/1b44d4b22a10c70558bc71fded5e7c9a/5c837/gemma-bench-new.png 1200w,
/static/1b44d4b22a10c70558bc71fded5e7c9a/2f4db/gemma-bench-new.png 1800w,
/static/1b44d4b22a10c70558bc71fded5e7c9a/84976/gemma-bench-new.png 2400w,
/static/1b44d4b22a10c70558bc71fded5e7c9a/42afc/gemma-bench-new.png 2522w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/1b44d4b22a10c70558bc71fded5e7c9a/5c837/gemma-bench-new.png&quot; alt=&quot;Gemma ranking on lmarena.ai&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Gemma ranking on lmarena.ai&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;And importantly, some of these are efficient enough to run locally, even on mobile hardware.&lt;/p&gt;
&lt;h2&gt;Your Options for On-Device Android AI&lt;/h2&gt;
&lt;p&gt;Okay, if I this your attentionc, then you might be wondering how to do that. How to run LLMs on a device? Right now, there are essentially two officially supported paths for integrating on-device Generative AI into your Android app:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. The System-Wide Approach: Google AI Edge SDK&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is Google’s integrated solution, designed to be the easiest path for developers. It leverages components built directly into the Android platform on &lt;em&gt;compatible&lt;/em&gt; devices.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pros:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Simple API, very similar to other Google AI SDKs (Vertex AI, Generative AI SDK).&lt;/li&gt;
&lt;li&gt;Platform handles model downloads, updates, and hardware optimization.&lt;/li&gt;
&lt;li&gt;Built-in safety features.&lt;/li&gt;
&lt;li&gt;Supports LoRA adapters for efficient fine-tuning.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Limited Device Support:&lt;/strong&gt; Currently only available on select high-end devices (Pixel 8/9 series, some flagship Samsungs).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Significant Storage:&lt;/strong&gt; Requires specific AICore and PCS APKs to be installed and updated, and the base Gemini Nano model takes up ~4.3GB of storage &lt;em&gt;before&lt;/em&gt; any LoRA adapters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model Choice:&lt;/strong&gt; You’re currently limited to using Google’s Gemini Nano model provided by the platform.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. The Self-Managed Approach: MediaPipe LLM Inference API&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This option gives you more control and flexibility but also puts more responsibility on your app. MediaPipe is Google’s broader framework for on-device ML pipelines (vision, audio, text).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pros:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wider Device Compatibility:&lt;/strong&gt; Doesn’t rely on AICore/PCS, so it &lt;em&gt;can&lt;/em&gt; run on many more Android devices (even on iOS).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model Flexibility:&lt;/strong&gt; You can run &lt;em&gt;almost any&lt;/em&gt; text-to-text LLM, provided you can convert it to a supported format and the device has the enough resources (more on that later).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full Control:&lt;/strong&gt; Bring your own base model, your own fine-tuned model (supports LoRA).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;App Handles Everything:&lt;/strong&gt; Your app is responsible for downloading the model, managing updates, initializing the inference engine, and managing resources (memory!).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More Involved:&lt;/strong&gt; Requires a bit more understanding of ML concepts and model formats.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Potential Quality/Size Constraints:&lt;/strong&gt; You need to choose models small and efficient enough to run well on target devices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Built-in Safety (from the API):&lt;/strong&gt; You might need to implement safety filters yourself if required.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Gotchas and Limitations&lt;/h2&gt;
&lt;p&gt;Whichever path you choose, be aware of some current realities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fine-tuning is Likely Needed:&lt;/strong&gt; Base open-source small models can be quite “raw” or generic. You’ll likely need to fine-tune them (using LoRA or other methods) for your specific task to get good results.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardware Requirements:&lt;/strong&gt; These models still need significant resources:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Space:&lt;/strong&gt; &lt;a href=&quot;../ai-edge-sdk&quot;&gt;As we saw&lt;/a&gt;, the AI Core APK takes over 4GB just for the base model. Self-managed models vary, but even smaller ones are hundreds of MBs or several GBs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAM:&lt;/strong&gt; Inference, especially with larger context windows, consumes a lot of RAM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Processing Power:&lt;/strong&gt; While optimized, inference still needs decent CPU/GPU/NPU power for acceptable speeds.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context Window Limitations:&lt;/strong&gt; On-device models typically have smaller context windows than the big cloud ones. Passing too much text (long conversations) will lead to signifcant performance degradation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Narrow Use Cases:&lt;/strong&gt; Current on-device capabilities are good at tasks like text rephrasing, smart reply, proofreading, and summarization. More complex reasoning or generation is still challenging for the smaller models (though fine tuning can help a lot)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why Should We Care About This Now?&lt;/h2&gt;
&lt;p&gt;You might look at the limitations and think:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I can’t see these smalls beating cloud proprietary ones. Why bother?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And you’d be right. For now.
&lt;em&gt;History doesn’t repeat, but it often rhymes&lt;/em&gt;.&lt;/p&gt;
&lt;h4&gt;From Mainframes to the PC&lt;/h4&gt;
&lt;p&gt;Remember mainframes?
Well, I haven’t seen one. But back in Uni I heard that those were huge &lt;em&gt;supercomputers&lt;/em&gt; meant for serious work.
They were the only thing that could run actually useful computer programs. It would take a dedicated refrigerated room with super expensive accessory hardware. And people could communicate with them via a terminal. A real terminal. Those had a keyboard and a screen, mainly handling input/output for the central machine.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Terminal_emulator&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;From wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the early days of computing, with the advent of interactive computing, the prevailing model involved a central computer connected to multiple terminals. This configuration, known as the centralized or mainframe model, featured a powerful central computer that performed all the processing tasks, while terminals served as input/output devices for users to interact with the system.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/d3d33bf59611b459575ee04d6237f201/c87a4/terminal-with-mainframes.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 66%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAADJElEQVR42gEZA+b8APz2+P76/P////v6+8S7wLSqr7+1u+DZ3vPw8uzo6ufi5eDZ3ce/xa6kq62jqqyiqrSqr6qgpqiep6+lrQCuo6PFurzWzdDc1dnAt72yqLC5rrjg2d/7+fr8+vv+/f7////RytChlp+qoKnW0df39fb08fPt6e3d19wAbWZkeXJykIeKnpOZnpWboZifn5eenpWdraOrtKqytayzq6KopZykrqStxbvE5+Hm6OTo5uLl19PWsquvAJiNjpWLjKKZnK+nq7attLWttLavt6+psa2lrq2lramhqaWdpKKaoaqiqbCor6ifp5SMk4uDipOJkqmfpwBrY2WtoqTc0dTi2N3o3+Tq4+nMxcy/uMDHwMjHwMjJwsnNxs3PydDUzdXVztbMxc2vpq6so6vKwMrj2uEAPz0/l4+Q7OHj6N/j7OXq9u/3eXd9cm50W1pghYGIWFdcbm1zhoSKUlBWcW927efw3tXd183V3dPc5+DmADc0NZyUlevg4+LZ3PHq7+/o72lobjo6QEBARllYXkRESWVla7WyuUJBRkdITvHt9eTc49rR2ce+xt7V2wAmIyZaVVZqZme0q6+KhYloZmplZWpMTFNCQkgoKS4mKCwxMjdLS1EvLzNKSU7o4uunoqnOx8/Eu8Pb1NsAdW9yioKEWldYf3t9XVtdQ0JGv7nBqKWqZ2ZqQEBGmJedo6Gof32DKCcrKCcsqaOphH+Fs6mytKmyu7G5AJeOkresr15bXWViZHBsb1lXWq2nrJCNkWhna4OAhfj0+MjEyqyprnd2emxpbpuUm2tmaicmKUlGSnVwdgA3MzVFQEJRTlBKR0klJCYyMTRYVVlYVltmY2pFREptaW0qKSsVFhcrKy3BvcP58/uspqsdHR8UFBk3NzwAb2hsSUVIeXFzX1tbg36Bz8jM0szSzsfP6uPsko+VODc7MzM2JygrNTU4xL/G/fb8qqSoGBgbREJIsquzALersIZ9gn93eXVub5mSleTb4Ma/w7axtd3V24N/hFJSVmhnajAwMWVjZ8K5v83DyL20u2hkaHRvdd7U3H1u0je2gDnsAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/d3d33bf59611b459575ee04d6237f201/577d7/terminal-with-mainframes.webp 300w,
/static/d3d33bf59611b459575ee04d6237f201/93ccb/terminal-with-mainframes.webp 600w,
/static/d3d33bf59611b459575ee04d6237f201/1aa16/terminal-with-mainframes.webp 1200w,
/static/d3d33bf59611b459575ee04d6237f201/daa23/terminal-with-mainframes.webp 1800w,
/static/d3d33bf59611b459575ee04d6237f201/179f7/terminal-with-mainframes.webp 1907w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/d3d33bf59611b459575ee04d6237f201/37b9b/terminal-with-mainframes.png 300w,
/static/d3d33bf59611b459575ee04d6237f201/ab3c7/terminal-with-mainframes.png 600w,
/static/d3d33bf59611b459575ee04d6237f201/5c837/terminal-with-mainframes.png 1200w,
/static/d3d33bf59611b459575ee04d6237f201/2f4db/terminal-with-mainframes.png 1800w,
/static/d3d33bf59611b459575ee04d6237f201/c87a4/terminal-with-mainframes.png 1907w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/d3d33bf59611b459575ee04d6237f201/5c837/terminal-with-mainframes.png&quot; alt=&quot;1970s-1980s mainframe computers&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;1970s-1980s mainframe computers&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 675px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/9d32a4cd01dc1ac495372a5a5a9badc4/e22a8/terminal.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 88.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAIAAADUsmlHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEVUlEQVR42gFKBLX7AEw9MndlUGVXQ0Q/M0dAMkhBM0tDNkhBM2xiUr2ynKWXgb+shenUnbOjc4B0T0ZAKiIgFxERDg0ODRESEAA0KSiwlpWei29ORDdbTj9gUUFoV0ZvXUl3Y0x/aVCIcVWEcFOeh2Lj1pr//7j99LHz6q1yb1YSExEfIBsALCQrkHePsJR2UT4nTzolV0EoWkQpXkoxWUYvYk81aldAbltEXEs4knxW/eCZ99mT//GgsaJtBgcKFRYUAEE0OpJ4kJp/ZB4YDScvODxDTElVZSouNT5HU0dTZBYWKQ0MEQAAAHplQf/XjPLMhP/ikKuSXwAABBcWEgBOQUOOdI6ihWciGw9CUWVlbn2Fj6F7gIGan6GSmZxWT1ArKCYAAAGFb0n/25Dy0Iv/5ZaslWABBAchIBsAPzg9jXONpohpMScTanWAh4yLlJiWm52Pqa2mqauhoZGIT0g7AAAAjXZN/tqP8M+K/+KTrJVhCAoNIiEeADoyN4tyjaqLazovGGRtdnyEio2SmI+SjZyhn5SViop+ZEI9LAAAAJJ6Uf/ekfjViv/olKiRXgUHCiclIgAvKSyLcYywkW45LRdMVF9sdX1pa2NnY05walFsZEN0bEQyMCAAAACWf1PxzYfBpHnivX6mkF4AAAUvKyoAIh4ghGuFtpZzPjIcJCIdNTEmNi8gOzQiPzciRz4oSz8rIxsTAAAAqI5ey651moFdzalyqJFeAAEDMi4qABwYGHRfdMCge04/JRgQCicdEzYqHUg5JllIMXFcP4NsS5J7VLSXZ/nXjv/ilv3fk//kkpqDUw0MCRwaFgAJCQlcTFjJqH7LqWm0lWLEo2rFo2nHpGnFpWvBomu7nmmvk2Cih1iGcEhyXzxjUjNvWjZDOSMPDwsaGRYADw8UQTU5kHRZemI9bFY4W0guTz8oRjYjPzAfPC4dOiwcNikbNSkcUkErVkMqRjgmU0MtXlQ0FBUOGhoXACEfKSghIWhTQBgOCCIXEDImGT8vIkY1JkQ0JE48K0k4J04+LEc5KVtKNW1XOlpIMDgrIEc5JUlHLBETEgAxLjsaFRWYfV9JNiMyJRsvJBo4Kh84Kh85LCAzJxsxJBkuIBQ5KBo/Lh9RPSlsUzRKOShaSDaPgVk/OysAYFpYAAAEjXVbW0YvIBYRJBkRFAwIGA8LIhYONigaPzIjVkg0e2lLsZxuyrWC1L+I2cWM4MuM5c+JcWVDAJuUggAABG1dSYFrS00+LHdjRI15UaOOXr+nb9W8fOLJhuLJh9W+gr2mc5uHWHtrRFlLMDQqGB4WChgUDwDKwKwNDhU9NjLXtXnYtXXHqG6xlF+XfU57ZD9aSS06LhwiGQ4RCgQDAAACAAADAQEGBQUMCwkcGRchHRsA6NvELiwtGBcaLiQYHRYOCwcFAQEAAAAAAAAAAwICBgYFDg0LEhAOFRMQFRIPFBESFxQWEQ4PFRIXHRgb8dtt4DR/mmYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/9d32a4cd01dc1ac495372a5a5a9badc4/577d7/terminal.webp 300w,
/static/9d32a4cd01dc1ac495372a5a5a9badc4/93ccb/terminal.webp 600w,
/static/9d32a4cd01dc1ac495372a5a5a9badc4/2703c/terminal.webp 675w&quot; sizes=&quot;(max-width: 675px) 100vw, 675px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/9d32a4cd01dc1ac495372a5a5a9badc4/37b9b/terminal.png 300w,
/static/9d32a4cd01dc1ac495372a5a5a9badc4/ab3c7/terminal.png 600w,
/static/9d32a4cd01dc1ac495372a5a5a9badc4/e22a8/terminal.png 675w&quot; sizes=&quot;(max-width: 675px) 100vw, 675px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/9d32a4cd01dc1ac495372a5a5a9badc4/e22a8/terminal.png&quot; alt=&quot;Computer terminal&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Computer terminal&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;Then came microcomputers, first seen as hobbyist toys on which one could play around, but not good enough for any serious work. We know how that turned out.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 816px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2711924c320c91221380e2354bbcfe65/f04a6/microcomputer-2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 70.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAADYUlEQVR42gFWA6n8AFAVFVsVF2MUGGoPFIMUGJwYHbMgIcYnJtMtK9cyMdcyNdY3OtE6O8k8O7s5NaAlJY0fH38ZGXAXFlsVFQBZFxhpGBp2EhmHJSGgOi+rQzaoSTigTTyaUT6RUT6ESzt5SDt3STt1Rzl1TT6oLzKdFh2DFhlxFBdaExUAYhkadhQZkkYqbFU0IyAUGhkQFRkQFhoTFBcRDRALDA4LCQ0JFhUPMCIYJyQYoDw6tRgfkxYaexQXYBQWAHcYG4tCJKiEP29ZNQkLBRAQBg8NBw4MBxAOCy4vJiYlHhIPDBsXETsnHC8mGoo+N9IYIawRG48QGGsSFwCFLx+Vdja3kE6LbkIWFwwtJRkYFRIQEAoVEg0SFA4gExEaEA4SEw03JRsvIhZtPjLUNyuuJBibHRZ1EhIAhFQqo4lIupVZo4RTGxoNFhYLCAkGEhELEhINFRAMNhISHREPDxEMMiQaOiQYVkIzvZhhwplYxZtavY9VAH5ZK6SGR7qWXrKPXC0oFxYWCggKBwwPCjAmEEEcETQOERwREA4QCyYdFj4qHD8sIMavjOPHms2whrWYcAB2RySihEW6lF28lGA8Mh4VFQkPCwdKMhRxTR9SKR9CHyggGRcODwobFhBOOixVQTJ6W0VwQy07IBUmEwwAaT4gpX89s4pRyJpfSTkjHBYLRTUdWUYnRTskLycgJx4bGhURFBEMHBUOTzYqSzgrTTYlVSwcLxEMMRAOAD8gFopmL7yNS9GeXllDJxAPCSgiFiIdFiwjGUQ2JFxEMG9QOnhcSHZHN4ZhRnVYPkInGTgmG2M1JctZKQCGGhtlHhmJXzTJmFSHZ0ASDAlCMB1vUjmcZke/fle8hWHPiGfGg12baktbTzZ1Vj5GLB1CJBRrOyHnkD4AsSIfoRkdfRQZfDklsIZWhXRVNiEYhVI4m2VLjl5EgFZBZkM2TS8lSCUeVCsdbz8lnF0yyH095pNF7ZxEANo6Itk8JNtAJsc0IZYkHJpbQ4FoUTcmGjQiHTYcGlUoHnc+KaZeOtCETe2gXfu0aP64Zf65X/m2XfSuWgD2ayb4cin4gC78jTL6gjTLVCihSCyERSmtYDXcikr5p1//u2v/vXD/vXD5uW7ytm/0tWv5tmf5tWP3s2GFW/paK1QOZQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/2711924c320c91221380e2354bbcfe65/577d7/microcomputer-2.webp 300w,
/static/2711924c320c91221380e2354bbcfe65/93ccb/microcomputer-2.webp 600w,
/static/2711924c320c91221380e2354bbcfe65/47c3c/microcomputer-2.webp 816w&quot; sizes=&quot;(max-width: 816px) 100vw, 816px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/2711924c320c91221380e2354bbcfe65/37b9b/microcomputer-2.png 300w,
/static/2711924c320c91221380e2354bbcfe65/ab3c7/microcomputer-2.png 600w,
/static/2711924c320c91221380e2354bbcfe65/f04a6/microcomputer-2.png 816w&quot; sizes=&quot;(max-width: 816px) 100vw, 816px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/2711924c320c91221380e2354bbcfe65/f04a6/microcomputer-2.png&quot; alt=&quot;Early personal computer&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Early personal computer&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;Does that sound familiar?&lt;/p&gt;
&lt;p&gt;I could go on and mention smartphones vs PCs and how there’s now a huge cohort of the population that uses these handhelds as their main computing device. Smartphones are still not faster than laptops or desktop computers, but they’re good enough for many people. You get the point.&lt;/p&gt;
&lt;p&gt;The pace of improvement in AI, both in model capability per size and in hardware efficiency (TPUs/NPUs on chips), is incredibly fast. The gap between cloud and edge in some areas is shrinking fast.
What seems like a “toy” or niche capability today could be a standard, expected feature on mid-range phones in 18 months.&lt;/p&gt;
&lt;p&gt;So in my opinion, getting familiar with the concepts, APIs, and challenges &lt;em&gt;now&lt;/em&gt; may pay off in the future. Besides, I’d argue it’s pretty fun to build with.&lt;/p&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;So, what’s the takeaway? While the AI Edge SDK offers the simplest integration, its device limitations are significant &lt;em&gt;right now&lt;/em&gt;. The MediaPipe LLM Inference API offers broader compatibility and model choice but requires more heavy lifting from your app.&lt;/p&gt;
&lt;p&gt;My recommendation?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prototype &amp;#x26; Validate:&lt;/strong&gt; Use tools like Google AI Studio or cloud APIs &lt;a href=&quot;https://firebase.google.com/docs/vertex-aiFirebase&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Vertex AI in Firebase&lt;/a&gt;, to validate your feature idea.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consider On-Device:&lt;/strong&gt; If your feature requires privacy, offline use, low latency, or if cloud costs are a concern, &lt;em&gt;then&lt;/em&gt; explore the on-device options.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Choose Your Path:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;If targeting only the latest high-end Pixels/Samsungs is acceptable, &lt;strong&gt;AI Edge SDK&lt;/strong&gt; is a good choice.&lt;/li&gt;
&lt;li&gt;If you need wider device reach &lt;em&gt;today&lt;/em&gt; or want full control over the model, &lt;strong&gt;MediaPipe LLM Inference API&lt;/strong&gt; is a route, but be prepared for the extra work of model management.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The “genie” of powerful AI is definitely starting to squeeze into the “bottle” of our mobile devices. It’s not fully comfortable in there just yet, but it’s making progress fast.
IMO it’s an exciting time to be an Android developer and lead the way. Make no mistake, competition will soon &lt;em&gt;innovate&lt;/em&gt; and make a huge fuss about this too.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Other Resources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/ai/gemini-nano&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Google AI Edge SDK Documentation&lt;/a&gt; (Official Docs)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MediaPipe LLM Inference API&lt;/a&gt; (Official Docs)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Related articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;../ai-edge-sdk&quot;&gt;AI Edge SDK Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../gemini-on-android/&quot;&gt;Gemini On Android - The Story So Far&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Prototyping an AI APP Series. Parts &lt;a href=&quot;../prototyping-an-ai-app-part-1&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;../prototyping-an-ai-app-part-2&quot;&gt;2&lt;/a&gt; and &lt;a href=&quot;../prototyping-an-ai-app-part-3&quot;&gt;3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Putting the Genie in the bottle]]></title><description><![CDATA[On October first, Google opened up the public beta of the AI Edge SDK. This SDK promises smooth integration with the new on-device AI…]]></description><link>https://iurysouza.dev/ai-edge-sdk/</link><guid isPermaLink="false">https://iurysouza.dev/ai-edge-sdk/</guid><pubDate>Sat, 09 Nov 2024 14:22:20 GMT</pubDate><content:encoded>&lt;p&gt;On October first, Google opened up the public beta of the AI Edge SDK. This SDK promises smooth integration with the new on-device AI components on Android. So, in case you want to build AI features that deal with sensitive user data or that do not require internet access, this will likely be the way forward.&lt;/p&gt;
&lt;p&gt;If the above sounds like a bit of gibberish, or maybe if you haven’t been following what’s going on with AI on Android and how we got here, you may want to check my previous article, &lt;a href=&quot;../gemini-on-android&quot;&gt;Gemini On Android - The Story So Far&lt;/a&gt;, where I talk about that.&lt;/p&gt;
&lt;p&gt;This time I went through a bunch of docs, official blog posts, and announcements about the foundational on-device AI components the Android team is cooking up and finally tried it out on my own device. Here are some of my impressions and my initial hands-on experience with them.&lt;/p&gt;
&lt;h1&gt;Quickstart&lt;/h1&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.android.com/ai/gemini-nano/experimental&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;docs&lt;/a&gt; are pretty good to get you started, but here’s the gist of it. As of now, using the AI Edge SDK requires just three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A compatible device.&lt;/li&gt;
&lt;li&gt;A valid &lt;strong&gt;beta&lt;/strong&gt; version of the &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.google.android.aicore&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;AICore&lt;/a&gt; APK.&lt;/li&gt;
&lt;li&gt;A valid version of the &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.google.android.as.oss&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Private Compute Services&lt;/a&gt; APK.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To achieve this, follow the prerequisite steps on the &lt;a href=&quot;https://developer.android.com/ai/gemini-nano/experimental#prerequisites&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;docs&lt;/a&gt;, and then add the Maven coordinates in your Gradle build file and you’re good to go. To speed things up, you can download their &lt;a href=&quot;https://github.com/utzcoz/ai-samples/tree/improve-sample-toolchains&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;sample app&lt;/a&gt; (this fork includes a small fix to the build setup) and run it.&lt;/p&gt;
&lt;p&gt;In my case, the compatible device is my new Pixel 9 Pro, so my impressions come from using the SDK on it.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; margin: 0 auto; width:50%;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/74c26dc61b086589608b7cf4dbe3f3ba/f3a97/blog-pixel9-pro.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 177.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAIAAAAGkY33AAAACXBIWXMAAAsTAAALEwEAmpwYAAAHv0lEQVR42lWU+VPTdxrHv+N0dZndTmu5gqKo223drbZObXURRHBVrgQiIZAEkkAOSDiFnFyKnLqgxKtV6wkaCCbkDrnvfHNf5OCwWtvZbndnd/+H/SRMf9hn3vOZzzeT1/N+vs/zJFDHyBi+u6cO115PYGKbO3Dk7ibKpWZqH5HWT6SzWhicVia3uOQMprG1mz+LxhBRtbj6Biq6jlyDboaGro6zhrkkLhuIwuO0DQ3SeYPtQ8OMwREGbwiQtC5+YVHJEHtQo4Z7eDNIFA5ZQ7iIATARGuYOXmZz+BwenzeA77hE7GK19HIp/Xwae5DeP0Dp5NO7BopOnxnt46tWrJOCJTK1v7K64SKmBVVLhHA0Fo7KaqL2XcTTUI3UelIngdZHbGeTGZwWJpfSNUDvGSwqKR1k9cjl2g72NXQdqRqFR9e1ImuaofEpgeDWHI092krmEIidqKZ2HLW3mcEmdXBbOgDMp/cMFJ4uneR16FSqy39baGnjXKioB3AVqgkSiR7cf/GQPjGFpTErMZRqXFt9Szee3t8EeCantZNH7eaXlf21HttE7xoGfa2owmIa6TVoUkUVHiLzpmpbmdXNtPMY4rkaIrKBhiV1kdpYNCaXmOJT5ufOn0c3tF4VLA9NPmYNCeoa2qqQTeWVOKhz6sXEzJOnM4Kx4fHh4fEhzsgI58rI0PgY9yqxndVA68fT+74+WXhlalbjej19V9bFnui4NHbuAvZCRSPUO3lXrxf9tOlejziSYXsi7IyGHAGvxeU0mI0qtVKiUkiuT1wRPJhXO99QOqeALePSZHEJ6kzZRYhC7rfq5G+ScDLk3Iq6I15T2GuMB20xv2U97FiPOt4kPG6LCk8g19YRv/jiqy+P/+X4V8WHD3/22Z+PQtdnvw24jd/H4fWIa3MNjvotAdjgd+mdJmXQbYiH7EBRn+XePcGNm9fBefv2rODmtVs3rwluTEKYB+8cvsAPCfdm3LOxBm/G3Ftxz7bA43rUBQQueoNGpVGoNHKVWqZSSRXyZdmKEBrlCPwuy1bcHfaaNqKuZNgRD6bckhFXIuSMh0DlrjdJn0q5siQSSlaWViQiqXhZLH4pEj2Hbnew4z6rx6ZOhOz//sfmf37e+NdPib+/jfy4FXi77nsd96Zhv0a18kL4XCoRicXCpeX5V2lBw0xezGuNBSz//DERC9r16mXYqlwLmLbizp/fBv/7y8Yv7+LvNoJqpfjJ0+8WhfPkuc7Sb/GCl3MK8SI01juy5rWth2xB2EBoxCKrKtE1yDp0LQGHpVNIK4uPt2LwD+v+VZX40cNvLo31Itq/Lpg+e3GO/PLlY6ifNRP12ZJBMK/F6goQ5ShkNbqmtgZVfbasrKOdajdIY36zUSN5cI1/b6r7MPPkx6OltZer7wv4UCf/0VrQFfMaQ7BRcH2MQSW1UUgUEoHQiKlDIwUzY4tP74GZeY2vFmd7JHe4RfSTh5nH5m/3qu5xIXQDOeIxR1z6zSicCNh91lWXSekwyG06qVW7YtNKPVZNMuyK2OXWZ5cdwonv7nTduc3QP79seTkOoWuqUt22qDajru/j3tcxDzi3L1trbpBxcy01+dWVBUZ96XQPdn6cIZ3rV9xl6x8NQgtX+xJ+q9MgWw87AfA66gZLCjAgMPaNiAt8Di5L8w937crYuXPn4Y8PnTh25HzRcUx5MbTAag3DhqBdvR50APJ1JO0GlMbAwidDjjW/RSZ6VlBQkJHx2wP79x3Yl78/P3/f3nyop/J00KmDjdJkwLYVgTfDKbeUYSSFJYL2WNAWchtWpcJPP/njjh07ELnZuTlZ4NyDyIHIZwv99lXrqijmtWyACkPOtKEj5relSL8V2PqcWrNG/PnRI++995vsrKzMzI8yP9qdmbkbKi0+5bdrjEph1G0ClScD9mTQDvofcOoiHlPUa4l4zW6b2mlUnCo8sXPXrtzc3KysVAKQAmLWVQZsmlXJ8zCsTwC3tMDM3WZV0KUHfNhtdFvVXpum/FxZRkYGgLPTARJAczSs16LSyxaCDm3MawbFr6VkBgxQCDYEXHowatiixKCR7//+/T15ebk5Obk5KRzioM96zEqLShhwaKIeAygeaNswRTp1focW/JO4TPKW5sYPPvgwP39vXh4iDwGUCzGrSmCjwih/EbApo7A27NKHXQaAgZoB6XNoPTYNbAY7J+1mUrOyssHA9uWDDHv27kFAQzgk2BCjbD5gUUYcumBawM1v1/psqx6rOkUaZTadZIDTm52de/DgwQMgwFlQAI0Sahw6qUH63GdShG3qgFXjs2q8VvU25jIpAGnRSczq5Ykr/G1nEAfSAQ1hK2xaiU7yzGuUhSxKn1npNqUE3sWul1q0EqN6WasQamQvbkyPgi7tT8c2Dw3jqs0qkfbVE6dW7DbKYIPMY1J40s7g1wKbFU6T3GmU27Xih3euIxCIbTLNFkBz7QTbqkQvnQffBlMB47VqxEaZcHX56eNvbkxdHezvYRCbGmqRFUWFJ8BG/1/ZD7tIS7emnk0NzLLaJxnNfDx6uBF1n46Ts+mnjnz6uw93Z2fnZGZlZ2XnIPL2HPg10l0rgGYay6frzk5iz9OLjnaVHKOf+pxXXrzAxC/2Nn/5p08Q+fmHDh36w68B7ikwFSn+f8yVgJIQjyscAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/74c26dc61b086589608b7cf4dbe3f3ba/577d7/blog-pixel9-pro.webp 300w,
/static/74c26dc61b086589608b7cf4dbe3f3ba/93ccb/blog-pixel9-pro.webp 600w,
/static/74c26dc61b086589608b7cf4dbe3f3ba/1aa16/blog-pixel9-pro.webp 1200w,
/static/74c26dc61b086589608b7cf4dbe3f3ba/963e9/blog-pixel9-pro.webp 1423w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/74c26dc61b086589608b7cf4dbe3f3ba/37b9b/blog-pixel9-pro.png 300w,
/static/74c26dc61b086589608b7cf4dbe3f3ba/ab3c7/blog-pixel9-pro.png 600w,
/static/74c26dc61b086589608b7cf4dbe3f3ba/5c837/blog-pixel9-pro.png 1200w,
/static/74c26dc61b086589608b7cf4dbe3f3ba/f3a97/blog-pixel9-pro.png 1423w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/74c26dc61b086589608b7cf4dbe3f3ba/5c837/blog-pixel9-pro.png&quot; alt=&quot;P9P 🔥&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;P9P 🔥&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;Once you’re done with the setup, you’ll get to do this:&lt;/p&gt;
&lt;figure style=&quot;text-align:center; margin: 0 auto; width:60%;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1114px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/94901e493b9e8abe324d6d31d8063626/90f49/blog-nano-demo1.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 96.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAABYlAAAWJQFJUiTwAAACu0lEQVR42n1U2XLaQBDkzbHBRheHC92A0GEkccsmzkOq8pBvygfkszvTC0OVK04emp4ddntnZmfUC4IAbduirmsURYHdbmeY2Gw2WCwWcF0XjuPAcZ0L/we92WyG4/GI8/mM9XqNrutwOp3MJe/v74Y9zxMxESWbg+4/IIKWZeHh4QH39/cYDAbG7vf7xr67uzP+4XAIW/Y5wozW9RyB/Qlc9KjqiTEajYRHV/YwnU4l/T12e0HdYLKu8OX3L7irJfxBhsQ+I7KO8O0SiXVGaG0xdmYXQcK2LVj2UGBhaElEjo3JZGwwnU7gCPebCtZ4BMfy4NoTuNYUHtlgbNLuufJjicA8LNDmZ9TZEc2qwzo7oclfsV5e1sQm3qIll28GbSH+XOpddmK/olrsRdD18DR8xLZ4w+kgG9oG3alDURbI8xzz+dwwX3uZZVgul2ZdFCVWq5XxF2WJdJ4gT+tLyk9Pj8iTBt8OP/G6+Y6vux/YlV+RhS3yeINVJBxtDFY3pq+92YVEXy879J6fnxFFMYLAR+CHCINI7AunSXrFHHGcIBFO4tSAvjSdSwYLRGFkHpQd0GPYbOqmaXA47FFK+Lpm+nW9RlWVZt22F9Bm2lEUgW0Xx7HpDNu20aMAN3Aq9tIiFHt5eTHNTR/B5uYEcR+bn2BtORQsGaeN7WYEq6oyk0IxgmIqRJHtdmuEyPRRjDbBqWJAFPeuU2QiPBwORkwPMUqNRKPnxXqJ/s8I+QZJknxMWdPTw9zMQ7xERTRK2upnNkzV9/2PKWuEKkoxTVtrSNDmXtoU5xeJD6MpG0H9TGnh9VNGNi99jZr/qaA+Dvcwyr9S1lppSlxzI/9Tn9aXrKK0WccPr8wI9YX1QTRlCpuRk3HLZOzYswR7kFHRF4ahSfsmyAMU07pQRCOhj7d/BgqpWJqmt6/6H3pZHunstyTHAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/94901e493b9e8abe324d6d31d8063626/577d7/blog-nano-demo1.webp 300w,
/static/94901e493b9e8abe324d6d31d8063626/93ccb/blog-nano-demo1.webp 600w,
/static/94901e493b9e8abe324d6d31d8063626/85941/blog-nano-demo1.webp 1114w&quot; sizes=&quot;(max-width: 1114px) 100vw, 1114px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/94901e493b9e8abe324d6d31d8063626/37b9b/blog-nano-demo1.png 300w,
/static/94901e493b9e8abe324d6d31d8063626/ab3c7/blog-nano-demo1.png 600w,
/static/94901e493b9e8abe324d6d31d8063626/90f49/blog-nano-demo1.png 1114w&quot; sizes=&quot;(max-width: 1114px) 100vw, 1114px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/94901e493b9e8abe324d6d31d8063626/90f49/blog-nano-demo1.png&quot; alt=&quot;Apparently Kant hates Dagger&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Apparently Kant hates Dagger&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;Hmm… maybe not ideal. Let’s try something else.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:60%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1126px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f7bcff967b9ddb64a175d1db44f68fdb/feacb/blog-nano-demo2.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 124.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAACXBIWXMAABYlAAAWJQFJUiTwAAADV0lEQVR42mVV6ZqiQAz0AdYZFRQPQFCUW0Tx2t33f63aVDQOs/MjXydNurqSTsJgMnEwGn1iGxY4Zn9wSG+o91eRm+rV7oJ8c0Iev2Rz+rJfa6Z6h0QwBpPJGI7jCtgDRVaiLErskhTpPkOeFYijDSZjB85kCteZyur+ENeZCYaDKrliMB6PMXVnKHdn+L6P9XqNIAgQhAFCkSDwEcURwnWodhStEYsdx/FLj+F5HoYfv5StMuTNbfnA6XTC5XLB4/HA8XhE0zS43++6x2+U2+2G8/mMtm3VZ7vdwpt5+PgcPgHHBlg80HXd+2BZljgcDnqYNlezCVTXta704+r7KxSb83fA6/WqDAhQVZWyIEie53qINvepWwT8vt/vsU02+phvwGN+1/DoRCFbY8ELCMY9AnIlU2O92+0wGn8ijY49wOL+ZmfMDJy2pcJCNj+uRVFgPvdQbruvRzmVv985JDCZ2GEDps49Y9i/NC8yrdk3w0ZCJgiT3GfJPTLgIe4TlLbpllPPmyFdv0OeScg3dBfeLrnpxLltJIeVAAloXSqDg+j1oZK1RnM86Jpme9EbLFae5LBhyBNpvREa6ZR79xfno9Rc06EVaWrJG22R1TzEahEiWEYq1P3lWveD1dOutvLKrutiJnS5WSTsyVaFusr2JaKXyVl0KZ29VEB6kTK5SL9fkcaNslsK+CDLsletZRpWUeYoqlxDyfK97KW6l+UpMtUzLP0F3JkjRKbw5lNpUx8f46GWzoCPwAewjmjb0+slL5I/KWARth59WJf8zjMs5jzLtQbZftPpFEzfgF3Al2KX8KAVt5UGL7Fysk7hSiAOEw4GDhSmjoNmQAc7RACCEtCGBGuSNi+k9NuOl7OEOHEIqAxtCNCJIP2hQLB+z/JS6xx+Z/7J0ACVIUM2BrzRCtXC5mHuE9zYWkTMIwfrt5DJyACt8q0LbOaROb/TptDf8htF0c8cMhy+oDW/sTQmFII+X/l5CdnSJthms/kCZFJtTNlktjxZ7voX2MpUkTVLJgzD7wwtZ3S00PrTmt+ZR37jy/eHBx/kGyDZkT7D4s+JNzIEOlk4XGk/f0wRkiTROlwsFhgOh7r3BqRzmqbqxEJdrVa66p9P5H/dhH52CW2CsQ7/AaOG33PYhsyFAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/f7bcff967b9ddb64a175d1db44f68fdb/577d7/blog-nano-demo2.webp 300w,
/static/f7bcff967b9ddb64a175d1db44f68fdb/93ccb/blog-nano-demo2.webp 600w,
/static/f7bcff967b9ddb64a175d1db44f68fdb/a28e3/blog-nano-demo2.webp 1126w&quot; sizes=&quot;(max-width: 1126px) 100vw, 1126px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/f7bcff967b9ddb64a175d1db44f68fdb/37b9b/blog-nano-demo2.png 300w,
/static/f7bcff967b9ddb64a175d1db44f68fdb/ab3c7/blog-nano-demo2.png 600w,
/static/f7bcff967b9ddb64a175d1db44f68fdb/feacb/blog-nano-demo2.png 1126w&quot; sizes=&quot;(max-width: 1126px) 100vw, 1126px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/f7bcff967b9ddb64a175d1db44f68fdb/feacb/blog-nano-demo2.png&quot; alt=&quot;Translation got commoditized&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Translation got commoditized&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;Ok, that’s impressive. My app can translate stuff locally now. What else?&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:60%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1114px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/e0f2c94253f64e00e0eb37bc9208b738/90f49/blog-nano-demo3.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 150%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAACXBIWXMAABYlAAAWJQFJUiTwAAADsUlEQVR42n1W27LaSAzkA8LNhs05gG0wGHzHY2MMSSqpfdr//6LeaYGMSVXy0CXNjNUjaSTByHFczOYzeKsQSdgg3hmctlWPeGt6ybPf19STXS3Y+wlGQjibok5/oD63OBcGZV7hUl9Fr8paMNTNuUHbdCLL3Ij0vS2Kw42EDubzOaqkQ9d1qGtrWJ1hTIWKeOrE9drK+SE64HiMkKSJSGK339oI6wchkR0u2O8P8HwPYRhivV4jCAJst1u7vxd9t9vJejKZYDweC6jPZnNMpmOk4eXlYXnqkMQp4jjG6XRCFEUiFdznRZRFUSDLMgHXm80G48kXpPsBYZXeUZvahlTb0Gz+zmcxpH65XGTfGCM6Jc/KssThcBBCd+HIowohXa6z77jf7mJI8PaqqsRYJQmapul17pOYpP98XdqXbl4emuSOW3frvVBPuR6S03OSEW3bigz8ABtv/fJQCFMS3pHnuRjRE0qCF1Bq2NQp9cKmuQjhKTDDsrnbkL9JWfCjNE0lnCEBvVRPhyk4Ho9YrVcoj92LsM6+ob1c+xzS8HdP9Yx7+nAkNRUfKUcxJKSHjbmgOlcSIvNTFKU1qlAWD6O2fbw4L6N8pIJ6a0Pe2DY0wxzepFMqYz1p7I1ljrzIYOoKaRbbrrD1GUeIk5Pscy/NEts1eznzAvsoOxLOHVvtYxTRFT+7/3Crf+Fa/exxb/5FZ35h72UIN6mAuq5Vj4IC5+N3jD4/P+F5HgIvQBQmFvFD7p94rk9R9sLBdshTj485wm2E9YePr8tPjNhObLPINrzjzKXiXdcR6bhzCwfL5QKLHu4Tj7W7cG2f+2I7m80wIhkHAbFarUCPPz4+3iTzPJ1OxYAY6hwQjHC5XMpbjDhBWMwES0BfkaVBUOelkpbn9NFJRFtKnruuKxePkiQRsmExc00ykmvNkZilxLV2jLSdJeQ06gm1G9QjbT1K9jCNeKlepuON33BvsVjItOlD5iGN1QttLR2y/Nj3fQG9YejMtQ5m5pH7PSH7UAcAPeKt2gk6qrT9GDLDpdfMH3NHsjdCDYly2MPcY4jDwcBzRkPJMz4QvX0j1Ach+OFwTOkwePR10edZhwVLijmkt5Q9oX6ohhqyeqtDl+fMtU5r7tOe+SahvPLwNTVv+iOkIevIUnLNIyWL/+2VaTisOfVK88Vz/cHS3xAtGa3DN0Ie6E8iH4J1Ruhay4OFOwT3NEyW1Fsdsjf/BH70N/AbvnZPOPxHoNDaUmhR/2mPukbyP1CQYXCQM1ddAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/e0f2c94253f64e00e0eb37bc9208b738/577d7/blog-nano-demo3.webp 300w,
/static/e0f2c94253f64e00e0eb37bc9208b738/93ccb/blog-nano-demo3.webp 600w,
/static/e0f2c94253f64e00e0eb37bc9208b738/85941/blog-nano-demo3.webp 1114w&quot; sizes=&quot;(max-width: 1114px) 100vw, 1114px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/e0f2c94253f64e00e0eb37bc9208b738/37b9b/blog-nano-demo3.png 300w,
/static/e0f2c94253f64e00e0eb37bc9208b738/ab3c7/blog-nano-demo3.png 600w,
/static/e0f2c94253f64e00e0eb37bc9208b738/90f49/blog-nano-demo3.png 1114w&quot; sizes=&quot;(max-width: 1114px) 100vw, 1114px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/e0f2c94253f64e00e0eb37bc9208b738/90f49/blog-nano-demo3.png&quot; alt=&quot;Right…&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Right…&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;Alright, let’s get back on track. In just these few paragraphs, you may have noticed a new word salad: &lt;code class=&quot;language-text&quot;&gt;AI Edge SDK&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;AICore&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;Private Compute Services&lt;/code&gt;. Also, notice that we haven’t mentioned Gemini so far. Let’s break that down.&lt;/p&gt;
&lt;h1&gt;On-device AI Architecture&lt;/h1&gt;
&lt;p&gt;There are basically three components in this architecture.&lt;/p&gt;
&lt;h2&gt;1. AI Edge SDK&lt;/h2&gt;
&lt;p&gt;So, that’s how Google is calling it in the docs. But, you know, &lt;strong&gt;naming is hard&lt;/strong&gt;. So although the “product” is called &lt;code class=&quot;language-text&quot;&gt;AI Edge SDK&lt;/code&gt;, internally, this particular &lt;a href=&quot;https://maven.google.com/web/index.html?#com.google.ai.edge.aicore:aicore:0.0.1-exp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;artifact&lt;/a&gt; seems to be called &lt;code class=&quot;language-text&quot;&gt;aicore&lt;/code&gt;. Anyway, it’s a good thing that it has a very generic name so we don’t tie ourselves down to specific model names or technology. Remember Bard? &lt;a href=&quot;https://blog.google/products/gemini/bard-gemini-advanced-app/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;RIP&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.google.ai.edge.aicore:aicore:0.0.1-exp01&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you’re wondering about where this &lt;em&gt;edge&lt;/em&gt; comes from, it’s related to &lt;a href=&quot;https://en.wikipedia.org/wiki/Edge_computing&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Edge Computing&lt;/a&gt;. This is basically the idea of offloading work that usually happens on a centralized server to the edge of the network. So in this case, we’re talking about moving the model inference from the &lt;wiki-link path=&quot;wiki/tpu&quot; display=&quot;TPUs&quot;&gt;&lt;/wiki-link&gt; in Google servers to your device.&lt;/p&gt;
&lt;p&gt;This SDK is how our apps can get access to on-device AI infrastructure, and it should be the only part that our app will be directly interacting with. These are the things you can do with the SDK:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check if the device is supported.&lt;/li&gt;
&lt;li&gt;Get access to the Gemini Nano model.&lt;/li&gt;
&lt;li&gt;Tune safety settings.&lt;/li&gt;
&lt;li&gt;Run inference at high performance.&lt;/li&gt;
&lt;li&gt;Optionally, provide a &lt;wiki-link path=&quot;wiki/lora&quot; display=&quot;LoRA&quot;&gt;&lt;/wiki-link&gt; fine-tuning adapter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The APIs for accessing Gemini Nano currently support text-to-text modality, but they mention that support for audio and image is coming soon.&lt;/p&gt;
&lt;h2&gt;2. AICore&lt;/h2&gt;
&lt;p&gt;This is the OS-level integration that Google has built. But why, you may ask? Good question!&lt;/p&gt;
&lt;p&gt;A huge problem for running inference on mobile is that AI models and weights take up a lot of space, and we’re talking multiple gigabytes of storage. So one of the main goals of AICore is to manage the deployment of models and weights and their updates (via the Play Store, like a normal app). This also allows a way for multiple apps to share the same model, saving a lot of storage.&lt;/p&gt;
&lt;p&gt;On top of that, we get built-in safety features (probably required by regulators), so you don’t have to deal with users trying to &lt;a href=&quot;https://www.reddit.com/r/ChatGPTJailbreak/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;jailbreak&lt;/a&gt; your app’s LLM.
Besides that, you get abstractions to deal with different hardware interfaces, so you don’t have to. According to Google:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AICore takes advantage of new ML hardware like the latest Google TPU and NPUs in flagship Qualcomm Technologies, Samsung S.LSI, and MediaTek silicon.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You see, you don’t really need Google to run on-device inference. People have been &lt;a href=&quot;https://www.reddit.com/r/LocalLLaMA/comments/1bpw9c7/running_llm_on_android_snapdragon_8_gen_3/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;doing it&lt;/a&gt; for some time now, but it’s far from an ideal flow, especially for anything production-ready.&lt;/p&gt;
&lt;p&gt;That architecture looks a bit like this&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/241dd/blog-ai-edge-components.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 57.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAACbklEQVR42l2T20sUYRjG/es6UJZ1Ud0WUUFQFFF4Wg+UopaHJIoihIpuI9CLICo0XVI31Ex31z3OzmFnZ3YO7q7rHn99M4pbDTzDx/Dx432e5502xGO5LpKmIWezKLkcqpCi62TEN80waDabNIRyZh7HKmLqDtu/d4hHU1jmLqbpkrcdD0Wb99KMHLG05GszssOvcIRIIklcypCUZar1GjRhMxlh4NMwnXP9BOYe0jM7yP3ZXqa+PRcD5P4GGshaBk3PEI5usrW9TlqKkTUU0qpC7RC4JUWZDr5gaukZr1ZmePnjNeOLT5lZfYecy7aAumnwJSTzYT7LbNDw9fG7ztySSjyjUq1WqVSqIg4dq1BitwSSaotoirgFMERkmmAcAdWcwY0pg+NdDu19NqcDQr02Z/osFjd09ooOpb09Ycsi4QRZNm4zn77DQuYWP61uUvmocGm1gIpucH3S4ES3AAYOgKcE8Gy/xcK6zn55l4qYUtZN0m6IVb2boNTLktTFWn4YyYoLYP4/oJjw0pDL1QmHK+Mul5+4nB+0+bwsUSrYwnYdKauiOhbhpMFGRGY9LBOXbRTb8Iv9B+hZ7uh3uDgk9MjhgtC5QYuvIUUUUhHFNEgpCsmsTCqXIaGlhVKkvbOS8dfsCCgL4DVh+WSPI3Kz/Rw9657l+TXvYs2/XG802BfWq/U6haIop1j0z5Vazd/VVilih25OGxw7LKX9MMOOAa8UzQd69xVFJZlMIYn9jMXivrxzQuysaf6Voe06bOxoLG/prGy3FApnxd9zkE25XGZkbIIHnQFGH08yMjrB6Nikr7v3Onnz9r0P/AOAg/z+mBjk7AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/577d7/blog-ai-edge-components.webp 300w,
/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/93ccb/blog-ai-edge-components.webp 600w,
/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/1aa16/blog-ai-edge-components.webp 1200w,
/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/daa23/blog-ai-edge-components.webp 1800w,
/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/e31a2/blog-ai-edge-components.webp 2394w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/37b9b/blog-ai-edge-components.png 300w,
/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/ab3c7/blog-ai-edge-components.png 600w,
/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/5c837/blog-ai-edge-components.png 1200w,
/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/2f4db/blog-ai-edge-components.png 1800w,
/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/241dd/blog-ai-edge-components.png 2394w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/ced0bf3d31a8fe6dc6dfc130a8b4e1c6/5c837/blog-ai-edge-components.png&quot; alt=&quot;AI Edge Architecture&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;AI Edge Architecture&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;h2&gt;3. Private Compute Services&lt;/h2&gt;
&lt;p&gt;AICore is &lt;a href=&quot;https://security.googleblog.com/2022/12/trust-in-transparency-private-compute.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PCC&lt;/a&gt; compliant, meaning it’s super secure, and cannot directly access the internet. Any requests to download models or other information are routed through a separate, open-source companion APK called &lt;a href=&quot;https://github.com/google/private-compute-services&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Private Compute Services&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Privacy through Data Isolation&lt;/h3&gt;
&lt;p&gt;This component helps protect your privacy while still allowing apps to benefit from the underlying models.&lt;/p&gt;
&lt;p&gt;A good example is the &lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPENbLC7V2T2KSKhChAYJR4xcB-Q_R_QL35E5ftvpKMiSUoEOBbL5i8bFj9isF90L7SnCGCB0lXWfbUcTXHw0tWLJz14XBk8ahy-feCyZPmuug-psYXWnLx55eVr__yNifj3TKv-dKU3SXwbXOLnt0D5orPkNtaz6BLcuSfDczcXdw8V4O1g19rLUb_Zw/s1600/image3.gif&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Smart Reply&lt;/a&gt; feature which recently rolled out to WhatsApp. It uses Gemini Nano to suggest replies and without PCC, it would be able to read the conversation context directly. With PCC, the code that has access to the conversation runs in a secure sandbox and interacts with Gemini Nano to generate suggestions on behalf of the keyboard.&lt;/p&gt;
&lt;p&gt;This is also how &lt;em&gt;Live Caption&lt;/em&gt; and &lt;em&gt;Now Playing&lt;/em&gt; run. You can find out more about how in the this is possible in the &lt;a href=&quot;https://arxiv.org/abs/2209.10317&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PCC Whitepaper&lt;/a&gt;. If you understand this bit, you’ll finally be able to explain to your friends how these apps are able to do this without being creepy 🙃.&lt;/p&gt;
&lt;p&gt;With that out of the way, we can finally get to this full diagram.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:90%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/eaac0cb4006b872fe227de8227e455e7/cae04/ai-core-architecture.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 58.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB7ElEQVR42pWS62/TMBDA9///D3xDCCHBJopgokMbdO1a9d0kS5w0TyfOw4ntPBu6lnIqYUjwifPpdJLv53v4Lk7/yI/TqcpxIzQWSY4+Js685qjMtO9t/fu+k4tn73g8tm273+93u5anWFA9JcgxFtje8NhgsZHnvNnt6rqGmD/w4XAAW1UV0tBqufJclzGOXLT11Zk0eTQ3uq+SmDRVWeSsECzLaAfDM5AQ+KqqSRRHlGacMyFsbCtbfTibPizmElI2qjFeWnPZXT76Wyfs4CRJMMYZg5MRWTanU308ThRZUuzeIHh7Y7zpm6MVkfTw/Tez9xX1BtZaJ79av4BqGWOQHHB3sdRGI2UwCNYrBbmfRvjyRr26NR6kKMFurGvWehUhNXScLjPMCWYAXlmWSZqKohAwGSFIlMpGuFS8tRoYHiVIR3d3g8srsJ6qdTB0+wxTSjnnWZYJIUI/1GV7cr+QF2ir2WEYkrOmjFGadrBlWZqmua5LCEmSOE0pKKWJpEczObodaZMlBgcHSc4ZPwtEdPBwOPrc74/HE9M0PexjP/CDwHa9D/fk1bX14t3i5Uft9Rd/jcIsS6I4ieBHoriDodqmaaDyHSxA02nT7Iqi4IzleS7O2WCuVd2AlhVEtn9v2H/J09Nhv9//BND/kJFrmK75AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/eaac0cb4006b872fe227de8227e455e7/577d7/ai-core-architecture.webp 300w,
/static/eaac0cb4006b872fe227de8227e455e7/93ccb/ai-core-architecture.webp 600w,
/static/eaac0cb4006b872fe227de8227e455e7/1aa16/ai-core-architecture.webp 1200w,
/static/eaac0cb4006b872fe227de8227e455e7/f8cf3/ai-core-architecture.webp 1304w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/eaac0cb4006b872fe227de8227e455e7/37b9b/ai-core-architecture.png 300w,
/static/eaac0cb4006b872fe227de8227e455e7/ab3c7/ai-core-architecture.png 600w,
/static/eaac0cb4006b872fe227de8227e455e7/5c837/ai-core-architecture.png 1200w,
/static/eaac0cb4006b872fe227de8227e455e7/cae04/ai-core-architecture.png 1304w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/eaac0cb4006b872fe227de8227e455e7/5c837/ai-core-architecture.png&quot; alt=&quot;AI Core Architecture Diagram&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;AI Core Architecture Diagram&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;Now that we understand roughly how it works, let’s dive in.&lt;/p&gt;
&lt;h1&gt;Hands On&lt;/h1&gt;
&lt;p&gt;If you try to launch that sample app &lt;strong&gt;without&lt;/strong&gt; meeting the prerequisites, you’ll get this error message.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;txt&quot;&gt;&lt;pre class=&quot;language-txt&quot;&gt;&lt;code class=&quot;language-txt&quot;&gt;Failed to check model availability.
com.google.ai.edge.aicore.UnknownException: AICore failed with error type 2-INFERENCE_ERROR and error code 8-NOT_AVAILABLE:
Required LLM feature not found&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, after updating the AICore, the Private Compute Services apps, and opening that sample app again, you should see in the logs that the model starts downloading:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;txt&quot;&gt;&lt;pre class=&quot;language-txt&quot;&gt;&lt;code class=&quot;language-txt&quot;&gt;com.google.ai.edge.aicore.demo       D  Downloading model: 3063135863 bytes
com.google.ai.edge.aicore.demo       D  Downloaded 132720 bytes
com.google.ai.edge.aicore.demo       D  Downloaded 2090361 bytes
com.google.ai.edge.aicore.demo       D  Downloaded 2090645 bytes
...
com.google.ai.edge.aicore.demo       D  Downloaded 4358020858 bytes
com.google.ai.edge.aicore.demo       D  Download completed.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So the model is around 2.8 GB, but there’s more being downloaded, I hooked it to GlassWire and saw this.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:60%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 627px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2f711840476507edbb575ab212e5d59e/5b567/blog-glasswire.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 107.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAAsTAAALEwEAmpwYAAADQElEQVR42nVUXY8bNRT1LysST31Boi/wC0orUVXAP0E8gyqhigqJp36idltKVURfVhXQbkVQNtlMkslmvr/tsX049iTp7nZ3pKN77XvutX3m2iJsFWZFg8QAsT6NtTIYpwUmWYVRlOEwLRH19hQnIWZdj6NOImZMhJ1GUHUcgOT3cOSVNDgIY7xZrPEP4fy1ssPiZsMjDtsa465AxJiIeoNVp5CSsIVbKdEWGf3CEgBKB/oZETYSYSt3fMdLtfE5YlZJ/JdUCKXdYd5qLDuDpfeJzmLRGu+HEhhnNQ7zBiuFTQ4QNBoL5ohr9yyu3QO+uGs/wI0HFt/8avH1I4uvHg325kOL6/fhseVdJb4k9zpriY9+AM7Dpe+BT24Dn/8MfEZ8ege44vDTEDvLvfwj8PEtQOwdaTye9Hgxxwf4LQCeEs/I2Rs1eHZYc+587nPyfqcVuTEUVKOm6OehcTAKr8MFxk0OBYvqAq6DSPnbM7aIt/1gk411iBjrSLwbWzxJ6ZuhVdILIErXGvzdxaZdKtrO2mGOhao0JjPEw5zHyi1YHzk55QUQNfuKlg0L3D5mG9C+ZLIy1s9PlyusZjM8ZvXnrgndIsbJQNgNTviCLYZ3bYnjXuNOVGEqG/ySHnkZFuyvP9nEL5oer8oCf5SVL+hy5AmoE75wR1jqEFXf8UqtIKlYYWeIGV1yty0yaptSnwSVzeH22PJWyM0JesrjxsoOcgjjjsxBxx1qDuiip36t0nAxV0AbzTntfTfX8boqFvFcTjTMddbFhD8DqQPZ7kYGvfdzmn/5gIxU52XwcTPwzvt2BWvVI+cj4T7J5LKLobmLfco2jjOMVmvsl8OOm6aB6ocFnV/X9emChs1dFgWyLBv8ku/e8RpZ3WCf3Gy1RBRM8boa/nDX1JBSeq7LSZLE+76gtRdtf5j/mwUn/MsOf9Hf8p09mbsd+4Kqbf3ArbINDHpafzMm7JNpN9ySs8XObkhE0wmefPct1pMJjqMYQRBgGYaYBXMEiwXHc4TzAFVZ+ITtn7e7M8BrGMexl0HU1ODg6R4qajjmLt4WPd6VPd7mCgfOrzTe0B5QvFEL8NF5D77a0kpotljLU2qt8T/3xDWeRPqNFgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/2f711840476507edbb575ab212e5d59e/577d7/blog-glasswire.webp 300w,
/static/2f711840476507edbb575ab212e5d59e/93ccb/blog-glasswire.webp 600w,
/static/2f711840476507edbb575ab212e5d59e/e891e/blog-glasswire.webp 627w&quot; sizes=&quot;(max-width: 627px) 100vw, 627px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/2f711840476507edbb575ab212e5d59e/37b9b/blog-glasswire.png 300w,
/static/2f711840476507edbb575ab212e5d59e/ab3c7/blog-glasswire.png 600w,
/static/2f711840476507edbb575ab212e5d59e/5b567/blog-glasswire.png 627w&quot; sizes=&quot;(max-width: 627px) 100vw, 627px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/2f711840476507edbb575ab212e5d59e/5b567/blog-glasswire.png&quot; alt=&quot;GlassWire Network Monitor&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;GlassWire Network Monitor&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;It shows that just over 3 GB of data was downloaded using Private Compute Services, as they promised they would. Then, checking my AICore App, I you can see that it’s now taking around 4.3 GB of what is probably the uncompressed model + weights.&lt;/p&gt;
&lt;p&gt;The bottom line here is that &lt;strong&gt;a minimum of 4.3 GB of local storage&lt;/strong&gt; is going to be required for this to work.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:60%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 823px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/7997853c6c0e5a07fd1f5776d2ddfd6f/8d39f/blog-ai-core.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 111.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAACFElEQVR42qVUyXLTQBDVZySxY8daY9nad8mOLS8hVNgOfAOfyIkTFMWBouCSQ77m0d1YRqEKYezDU8+0ut+8me4Zxbq8xN9wPRhAOztDaBnwLRMqjdnXlaN0kekXPSIy8PHLA358e0Rkj8l30UnaSWj0evAMHe8/fMXnT98RXpvHEzYYD4eo0xhV4MOm8b/ilbai9sqNwlkQ4M12i/vlEsskgU4+idvF7/N2uYrZ78PVNNxEIVZpinkYoqZEnvO4cF0i9UllKuSl5+3j1nmGdZbJPLQsESCEDdrq2LKvrbwd084z+7//KXxGMVUvmdhICWy5mul0IuNDwRzMpbi6jtL3UHguQnKu0wTvXt4joHHmOuLPyea7MSNzptiUhRSq8TMHcyncFk3SXZHh9c0Mb+sFXpGt40iS+V9jOXYeBlgksZxzRUT8jwmZS3E0db/yLR3y8zLHJkuJeI4VJeUtlY2SORHNiFQsqWzymUvaZqqqcHQNE3UEezSSuVgKcMn/JxztKdjHOfs+5Ia1qZL2YCh4WsHDIYRM9oJ6aUt9taFt1NRnRqs9/heikBuSV2DLOJbsl8KrK6yoCHwjFjEVgSpmnqKQb8FtWeKuquQqedRLJxP6pil3ke3JW+aPdn4Ojd45ticTssJtUWARRfLKPJtVmFIPHrtteRyYaJPnUpiAtj0+4CHtVMiXms+QK85P/Clb/gnS8IOgBH0J8wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/7997853c6c0e5a07fd1f5776d2ddfd6f/577d7/blog-ai-core.webp 300w,
/static/7997853c6c0e5a07fd1f5776d2ddfd6f/93ccb/blog-ai-core.webp 600w,
/static/7997853c6c0e5a07fd1f5776d2ddfd6f/172ae/blog-ai-core.webp 823w&quot; sizes=&quot;(max-width: 823px) 100vw, 823px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/7997853c6c0e5a07fd1f5776d2ddfd6f/37b9b/blog-ai-core.png 300w,
/static/7997853c6c0e5a07fd1f5776d2ddfd6f/ab3c7/blog-ai-core.png 600w,
/static/7997853c6c0e5a07fd1f5776d2ddfd6f/8d39f/blog-ai-core.png 823w&quot; sizes=&quot;(max-width: 823px) 100vw, 823px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/7997853c6c0e5a07fd1f5776d2ddfd6f/8d39f/blog-ai-core.png&quot; alt=&quot;AICore Storage Usage&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;AICore Storage Usage&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;h2&gt;Show me the code already&lt;/h2&gt;
&lt;p&gt;For something so “exciting” the code you need for using is a bit underwhelming. I mean, it’s very basic. There are just a couple of things you need to do to make this smoother for the user.&lt;/p&gt;
&lt;p&gt;The main object is the &lt;code class=&quot;language-text&quot;&gt;GenerativeModel&lt;/code&gt;. It takes two parameters: &lt;code class=&quot;language-text&quot;&gt;DownloadConfig&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;GenerationConfig&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;GenerationConfig&lt;/code&gt; is where you define the main settings, like &lt;code class=&quot;language-text&quot;&gt;maxTokens&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;temperature&lt;/code&gt;, etc. These parameters are standard across LLM models and frameworks, so I’ll leave them out of this article’s scope. A quick Google search should get you in a good place.&lt;/p&gt;
&lt;p&gt;Putting things together we get:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; downloadConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;DownloadConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DownloadCallback &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onDownloadStarted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bytesToDownload&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onDownloadFailed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;failureStatus&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GenerativeAIException&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onDownloadProgress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;totalBytesDownloaded&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onDownloadCompleted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; model &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GenerativeModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    generationConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; generationConfig &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; applicationContext
        maxOutputTokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;600&lt;/span&gt;
        temperature &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.9f&lt;/span&gt;
        topK &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;
        topP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1f&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    downloadConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; downloadConfig
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is it. Some weird parameters, but that’s all there is to it. Now, before we’re good to go we need ensure the model is ready to use. Checking things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is this a compatible device?&lt;/li&gt;
&lt;li&gt;Is the model downloaded?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To do that, we can call the &lt;code class=&quot;language-text&quot;&gt;prepareInferenceEngine&lt;/code&gt; and handle the exceptions.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;runCatching &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;prepareInferenceEngine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onFailure&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GenerativeAIException &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
    Log&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TAG&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Model not available: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, you can generate the response synchronously.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;buildPrompt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or do it asynchronously; for simplicity, I’m just appending the newly generated text as it’s being streamed, like so:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; job &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; scope&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateContentStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;buildPrompt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; responseChunck &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
        result &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; responseChunck&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text
        &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Which you can cancel midway&lt;/span&gt;
job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that’s it. If you run that code, Gemini will start streaming down text. That is of course, if you ask it nicely.
What do you mean nicely? Well… it’s complicated.&lt;/p&gt;
&lt;h2&gt;Mic check, 1,2… is this thing on?&lt;/h2&gt;
&lt;p&gt;You see, I think it’s fair to say that most of things you’ll try with work straight away, but it’s likely you’ll hit some snags on the way. Eg.:&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:60%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/cc164b050e72a2e0597f69b442bd2595/ceffd/nano-security-feature.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 74.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAACZUlEQVR42o2UzVPaUBTFs+l0xiGfj0DAQAL5/iAgIBrRaquDMuN023W3/f/3p+eFoNjpwsWPc+7Ly827yb0ow+EQnufBdV2Mx2NMJhMMBgOMRqMmlt6yrE+jZFmG3W6Hu7s7bLdb7Pd71HWN+/t7PD8/Y7lcwjRNWELAsu2Tm0XLR6/IzYZhNOi63nBc63Q6UFUVOr2laRBfvsLiNVMYRG95901C+SOEeHuyEN1GHcdBWZRIWUESBBCzAtqf37D5aoZmBNcqMbJmOLcyaoWRWaLXdQ4JT9ENDarWgWkZCKPgwMSDFvk4+/UKo2/DVl30tIBMYeseejpV82Ea1seEpmkg8goU4QpZsEDszxB7xK+Q+3Pk/QTZdIEsnCMNK2p18MEMebRAOp0f3uEpm9kPrKoa60WNq+Utrla32FxssaFfLW5wSX+9/tbE63ndxFJlXC+eoHS7XfT7/ead9Xo2xuc+8nDZnCQPLpqnRuMSIZEa8cQHDmsN9LIS1/GhFEWBqqqQJAnKskQ1n6EoM5IjSSMslnP0HRsd9Qya3vkvuqFSZTfoUEZs3iTPEcYxQiZN6dO8QEaSNEWcZug5A7YFv37XbhDEJN0W03zvFCVgwozTkfo+CpK2xC3yekxSTpMkIiHJiU/G3CO1aX6ZcM3NNZv2kl94ywa+IdfkO3lkCTvqAzfvqa/khTyRn6Tmeo9J5LiK4+htOLMPnIhbTsiNruGRycf0gYQ3xUzqUyXTNo6oE5LQ29zny4THkqeyZB45aUuSfsoyzol7ou4/8aj1HpN5zPFWsjSCrXPEatX+JLLtTv9t/gIVHpGM458h6gAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/cc164b050e72a2e0597f69b442bd2595/577d7/nano-security-feature.webp 300w,
/static/cc164b050e72a2e0597f69b442bd2595/93ccb/nano-security-feature.webp 600w,
/static/cc164b050e72a2e0597f69b442bd2595/1aa16/nano-security-feature.webp 1200w,
/static/cc164b050e72a2e0597f69b442bd2595/bea69/nano-security-feature.webp 1301w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/cc164b050e72a2e0597f69b442bd2595/37b9b/nano-security-feature.png 300w,
/static/cc164b050e72a2e0597f69b442bd2595/ab3c7/nano-security-feature.png 600w,
/static/cc164b050e72a2e0597f69b442bd2595/5c837/nano-security-feature.png 1200w,
/static/cc164b050e72a2e0597f69b442bd2595/ceffd/nano-security-feature.png 1301w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/cc164b050e72a2e0597f69b442bd2595/5c837/nano-security-feature.png&quot; alt=&quot;✋ Stop! We don&apos;t do that here.&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;✋ Stop! We don’t do that here.&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;It’s unclear what exactly is considered out of bounds, and I think they’re being very conservative with these settings, which is likely to avoid some PR disaster. For an &lt;em&gt;experimental&lt;/em&gt; product, I think that’s fair.&lt;/p&gt;
&lt;p&gt;If you do ask it nicely, then I have to say that it’s pretty fast! Especially if you remember that this is the worst it’s ever going to be.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:60%; margin: 0 auto;&quot;&gt;
  &lt;video style=&quot;width:100%;&quot; autoplay loop muted&gt;
    &lt;source src=&quot;/ca5299162ca715ba1940d10c4ab3c308/gemini-demo.webm&quot; type=&quot;video/webm&quot;&gt;
  &lt;/video&gt;
  &lt;figcaption &gt;
    What 45 tokens/second looks like
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/br&gt;
&lt;/br&gt;
&lt;p&gt;Apart from security, you’ll notice another an issue when you start testing the limits of its &lt;wiki-link path=&quot;wiki/context-window&quot; display=&quot;Context Window&quot;&gt;&lt;/wiki-link&gt;.&lt;/p&gt;
&lt;p&gt;First of all, I couldn’t find any explicit information about Nano’s context window size. In the paper, they say that the models support a 32k context window, but I assume that’s obviously not the case for Nano running on a phone.&lt;/p&gt;
&lt;p&gt;Now, the paper also says that Nano is a &lt;wiki-link path=&quot;wiki/quantization&quot; display=&quot;4 bit quantized&quot;&gt;&lt;/wiki-link&gt; 3.25B parameter model. As we saw on my &lt;a href=&quot;../gemini-on-android&quot;&gt;last article&lt;/a&gt;, the Pixel 9 Pro has 3 GB of RAM reserved for LLM inference.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Back of the napkin estimation - don’t take this seriously!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;3.25B × 4 bits = 13 billion bits = ~1.625GB&lt;/code&gt; for model weights.
Given we have 3GB of RAM available, that means we need to stay within 1.375GB.&lt;/p&gt;
&lt;p&gt;I did some research and an &lt;em&gt;uninformed&lt;/em&gt; estimate could gives us between 2048 and 4096 tokens.&lt;/p&gt;
&lt;h2&gt;Empirical trials&lt;/h2&gt;
&lt;p&gt;Or &lt;strong&gt;Throwing things at it and seeing what sticks.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I prompted it with an excerpt of 4k &lt;a href=&quot;https://platform.openai.com/tokenizer&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;tokens&lt;/a&gt; from the Gemini paper to ask questions about it. It worked reasonably well. For reference, ~4k tokens is what you read from the beginning of the article until here.&lt;/p&gt;
&lt;p&gt;Then, I tried increasing that to around 7k tokens by adding excerpts from another paper and it started logging some weird errors, and I couldn’t get it to answer it.
Finally, I tried duplicating the original prompt, getting close to about 10k tokens and it worked(?)! Though it took way longer.&lt;/p&gt;
&lt;p&gt;As you can see, my math was off (as usual), but so were the docs and the error messages. Actually, that’s another big pain point when working with this, but at least they’re &lt;a href=&quot;https://issuetracker.google.com/issues/370937465#comment2&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;aware&lt;/a&gt; of it.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;stacktrace&quot;&gt;&lt;pre class=&quot;language-stacktrace&quot;&gt;&lt;code class=&quot;language-stacktrace&quot;&gt;AiCoreInferenceHelper
com.google.android.apps.aicore.base.InferenceException: null (statusCode = 4)
at biw.a(PG:1049)
at dgx.a(PG:13)
at duz.g(PG:3)
at dvb.run(PG:42)
at cou.run(PG:3)
at dsl.run(PG:50)
at amg.run(PG:50)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These are not super helpful. The internal components (things inside &lt;code class=&quot;language-text&quot;&gt;com.google.android.gms.internal.aicore&lt;/code&gt;) are obfuscated, so the SDK really works like a black box, which is somewhat expected since LLMs are, by definition, black boxes. Though, I’d like to see better API documentation, including the recommended context window size and information on performance degradation for larger contexts.&lt;/p&gt;
&lt;h1&gt;Making it smarter&lt;/h1&gt;
&lt;p&gt;Not gonna lie. Nano is not the smartest kid in the room, and it shows. The main problem is that On-device models have some hard constraints, like the limited parameter size and smaller context windows.
To improve their performance we can use fine tuning. Or more specifically, &lt;wiki-link path=&quot;wiki/lora&quot; display=&quot;LoRA&quot;&gt;&lt;/wiki-link&gt; fine tuning, which is like “fine-tuning on a budget”. You get similar results with way less memory and computation.&lt;/p&gt;
&lt;h1&gt;Press X to doubt&lt;/h1&gt;
&lt;p&gt;It’s no doubt that things are still very rough around the edges. You can certainly build products with it, like Google and other OEMS are doing, but there’s a few blockers that we can need point out:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Device support&lt;/strong&gt;: Just a few devices currently have the hardware to support this and those are top of the line ones. Android is known for cheaper models which make it ideal for users on a budget (most people). It’s still unclear to me how well the new hardware and increased memory requirements are going to rollout to these cheaper devices or how long would that process take.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Base models lower quality:&lt;/strong&gt; LoRA can help, but Nano is still not going to match what you can do with the models running on the cloud which limits the use cases a lot.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:80%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/5aad9b5da0c5ae3e0cf1b4b644c16196/77059/press-x-to-doubt.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB7klEQVR42oWSy09TYRDFv/vgPtpioVDbWtp6+5BHK1HEBqUQDVoJxMQHjQ0+iBJFQhqCqRi7M1G3dI9hWW339n/wj3Hjxp0/7r0tFWMCi5OZzMx3cuabI4QQ9Kl9+P1DyHY0dC+mcQZVC6AoKpLdFy6kLsTJkCTZThRMVSMbGmQhHWL39hil6QS67qHf1+8O/kt8IqlMQNeYiYcpXYxyyxpma8ZiYzaN1zSRJAmlz4M8PI/is3oPnbosyy6c3EG3JxgNeClPj7E9l+LVlRHezSV5a6vsDBgEF7+z8AEmt34igkU0RaAbZo9YVdXjCjvr5K0oL/MJ1nNhPt9IUrk56g4o5+6irMC9T7/IVECkD9iuvKbZarGz84Z6vU6j0aBarbpqXULTNJhJRZmPBVibjFBfGicbHewoGJjG9+g35b0/XH5vE8Y/8mz1AV+/tdjf/0K73abZbFKr1TqEmiSYuBDn4dUM+bMmpfEwK/lUbx0nDky8IFX+Qax4gNBDyHZNtR1x9IdHcFce8TuEEZaLszy/ZrH3tMD64zucD6r4DOXYVf+/7rFD/EUioJBL+CjeL3I9E2d3o8STtWWyEcFUykt0SLO95VzyiOAU21hBjdGIzuJSgYlEjM3NVQr5DLkRlam0l0tJD6aunG7oLg4BSGESEJJlN2cAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/5aad9b5da0c5ae3e0cf1b4b644c16196/577d7/press-x-to-doubt.webp 300w,
/static/5aad9b5da0c5ae3e0cf1b4b644c16196/93ccb/press-x-to-doubt.webp 600w,
/static/5aad9b5da0c5ae3e0cf1b4b644c16196/1aa16/press-x-to-doubt.webp 1200w,
/static/5aad9b5da0c5ae3e0cf1b4b644c16196/daa23/press-x-to-doubt.webp 1800w,
/static/5aad9b5da0c5ae3e0cf1b4b644c16196/9dd96/press-x-to-doubt.webp 1965w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/5aad9b5da0c5ae3e0cf1b4b644c16196/37b9b/press-x-to-doubt.png 300w,
/static/5aad9b5da0c5ae3e0cf1b4b644c16196/ab3c7/press-x-to-doubt.png 600w,
/static/5aad9b5da0c5ae3e0cf1b4b644c16196/5c837/press-x-to-doubt.png 1200w,
/static/5aad9b5da0c5ae3e0cf1b4b644c16196/2f4db/press-x-to-doubt.png 1800w,
/static/5aad9b5da0c5ae3e0cf1b4b644c16196/77059/press-x-to-doubt.png 1965w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/5aad9b5da0c5ae3e0cf1b4b644c16196/5c837/press-x-to-doubt.png&quot; alt=&quot;&apos;This ain&apos;t going nowhere&apos;&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;‘This ain’t going nowhere’&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;h1&gt;It’s a movie, not a picture&lt;/h1&gt;
&lt;p&gt;Now, the counter argument to that is that while those points are true, we need to understand how this environment is playing out.&lt;/p&gt;
&lt;p&gt;For example, what are some of the core constraints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Compute:&lt;/strong&gt; How &lt;em&gt;hard&lt;/em&gt; we train each generation of models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parameter size and training data:&lt;/strong&gt; More parameters generally mean that the model have a better understanding of the input, and more training data gives it a better understanding of how different concepts relate to each other in meaningful ways.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite all the valid criticisms you can have, what’s happening in AI right now is nothing short of absurd.
This stuff puts &lt;a href=&quot;https://en.wikipedia.org/wiki/Moore%27s_law&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Moore’s Law&lt;/a&gt; to shame. Doubling the number of transistors in a chip every 18 months? Neah… how about a 10 fold improvement over these core constraints every &lt;strong&gt;12 months&lt;/strong&gt;?&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:100%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/c5686ee5134aa7a4d9f79b4a976bf18e/3e970/model-training-compute.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 55.666666666666664%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABj0lEQVR42pVSyU7DMBTM/38GVw5IiC+AEwcORVQcWEolREOzNIsd7x6e7WztDUdPiZc3mRlPVtQ1jlWFvCgxSIkwvPf4z9DG4NR16DhHxocBfBCQSsE6C0+Pu6j1M82F9qiZxW+rkXcGPethrUUW0B0xCqTim1qcP6+4upqTBjTSY1ca3D194vpxg6qsItNMa52avF8Akb4TwMhs3vcwpOS7GfDyUePq5gGbpy3y4xEtyU4M3QrQubl5BhzXpzO9FHjbV7h/3uN2u8OuaFCU5STZzoCxYQU4rYVhrAMnNZbOC0O+FRyvB4avRqFiLsoNY2bo5uYFKOwFIDEosIZBCAWtDe05tMLj2Dm6FIeSLanIDNEM0gw1y0FCRRAb54xxCCWhlYYisCg7BYsAHX5aGyswXAADAB2UFB/WnCDIn3gpIUKz7CU8blQRGOZ9qopfMOxEnzI4+TjGw66siPOxAvCJG3zkPd4PHfJWLYBhU5HJIT7Js/PqKf1104ILEa3QtBZKkpecLGJUQlL0XPrpH86QXDgOfu6PAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/c5686ee5134aa7a4d9f79b4a976bf18e/577d7/model-training-compute.webp 300w,
/static/c5686ee5134aa7a4d9f79b4a976bf18e/93ccb/model-training-compute.webp 600w,
/static/c5686ee5134aa7a4d9f79b4a976bf18e/1aa16/model-training-compute.webp 1200w,
/static/c5686ee5134aa7a4d9f79b4a976bf18e/159cc/model-training-compute.webp 1666w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/c5686ee5134aa7a4d9f79b4a976bf18e/37b9b/model-training-compute.png 300w,
/static/c5686ee5134aa7a4d9f79b4a976bf18e/ab3c7/model-training-compute.png 600w,
/static/c5686ee5134aa7a4d9f79b4a976bf18e/5c837/model-training-compute.png 1200w,
/static/c5686ee5134aa7a4d9f79b4a976bf18e/3e970/model-training-compute.png 1666w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/c5686ee5134aa7a4d9f79b4a976bf18e/5c837/model-training-compute.png&quot; alt=&quot;Training compute (FLOP) used over time&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Training compute (FLOP) used over time&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;figure style=&quot;text-align:center; width:100%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ea7d63384847ecdcba81d3cd158f36f6/38574/parameter-size-growth.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 83.66666666666667%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAACbUlEQVR42o1U2W4TQRDc//8LniPeyQNC4hCRkEIMCQTFlhdfsb33fW9RNfYYK0oQI/XOsdPV1dc4ZVkiDEMkSYIsyxBFEeI4Pu3zPEeapubcrnXeNA3sqJsWry7e4MvNTzi6OJ/PsVqt4LouptMpPM8zilZZ4BLt7VrgAu26lucZLl5f4uuEgGKzXq8NgzSJTww1i7lda9b+5MFxb70bht6wdWRFB1FW4WEVIClq82PEy6PtOkQEzHO6XteGrYCF5YzjiJqHYZIhSEtUTXcA5PlTGYYBPcHSvYdKMa4bLLd7hEEA5aLjP0fKipPiY4EGq9wPaNuObGmULCreq+MEvz5+xuLdexRxiuvrG8xmM4YgOTAUSFEUxm3Lwsy0VpYV/L2PMi/xe3KLh8u3cD9dwf12i2zziI6svEfOfX8i49hSOAeUxGGCxWwB9/s9lqtHTCd3+PHhCjt3idIP0PNOQYPS6wmoRAnHETvrso2Vxmbr4+5+Do/Ay42HeOch4Fox7KvqryfHuW3bg8sClCiwclP0JWFaIKbkteosx0AFgUm5p+JIoOeGKZuaEvo+Grqgql9vA3g7HxkBC8aPPplEPc34s4D6KAYJXS7KGl6UsVNYwHJPJUUDOHPtvwA1kiyn6wwy26jj5YY11hFMxiSqMSt2/09AWVTNWaWKJVGwAtTXAeOrLFZMhkRh0vxiDM9fmVh9KbeMkZHgPVuNLIfj2Qjz374ypm2pu9/vjRFHYLvdzjDxmRj1pBgd5ND4DTMsYz7PVLdRdHgwVPjSk/52uzXt56iP7TOlyxL7FsqijZf2Et2Xou6p9mzZ2V7+A3E/IG6+e/v6AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/ea7d63384847ecdcba81d3cd158f36f6/577d7/parameter-size-growth.webp 300w,
/static/ea7d63384847ecdcba81d3cd158f36f6/93ccb/parameter-size-growth.webp 600w,
/static/ea7d63384847ecdcba81d3cd158f36f6/1aa16/parameter-size-growth.webp 1200w,
/static/ea7d63384847ecdcba81d3cd158f36f6/cceee/parameter-size-growth.webp 1222w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/ea7d63384847ecdcba81d3cd158f36f6/37b9b/parameter-size-growth.png 300w,
/static/ea7d63384847ecdcba81d3cd158f36f6/ab3c7/parameter-size-growth.png 600w,
/static/ea7d63384847ecdcba81d3cd158f36f6/5c837/parameter-size-growth.png 1200w,
/static/ea7d63384847ecdcba81d3cd158f36f6/38574/parameter-size-growth.png 1222w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/ea7d63384847ecdcba81d3cd158f36f6/5c837/parameter-size-growth.png&quot; alt=&quot;Parameter vs training dataset size&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Parameter vs training dataset size&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;There’s a &lt;a href=&quot;https://arxiv.org/html/2211.04325v2#:~:text=Our%20findings%20indicate%20that%20if,earlier%20if%20models%20are%20overtrained.&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;counter argument&lt;/a&gt; to these graphs, which say that we may run out of data before to train them soon. However, if you connect the dots with what Google specifically is doing for this to work on Mobile, then you start getting where this is still likely going. Just look at this.&lt;/p&gt;
&lt;figure style=&quot;text-align:center; width:80%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1056px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ad52f69610f6090af2e4d35a069e3aba/813c5/local-llm-evolution.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 50.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABdElEQVR42n2RuW6CQQyE/7dHKQKBilSkI69Amp+GIFEgEPd93/dZAZN8RislCspK1no99njs9fR9LpfLQ3Pner0+xIm7w/t2u8kjOJlMNJvNzPAXi4VarZai0VeFIxH5yaTW67Wm06kZObwzmYyCwZDi8XeLnU4neTBDtNlstFqt7N7v90YYegnrORhUIvGhw+Fg2Ha7tZviVOpTgcCTYrE3zedzHY/HOyFdISNYqVTUaDS0XC51Pp+12+2swWg0Ur1eV7FYVKlU0mAwMIw6bvKNkJHH47Gp7HQ6ymazyufzGg6HRlytVtXv940QwwfD7/V6Rk4eon4pZG/dbtdAjJ1QCIHbMWtgApRCSBxS/D8jQ0hxs9k0QzWJjOZ8iiAm3ymu1WpGCmafwp5QAlAul20EVDA+xKhqt9uWg7JcLmc4zQuFgnzfVzqdtsZ8lgczRCRxO1IXc3F3g93xkhGyc5qAQeqxNxaPodLZz/d/OKrd5zHJF+Qy1xOnDBANAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/ad52f69610f6090af2e4d35a069e3aba/577d7/local-llm-evolution.webp 300w,
/static/ad52f69610f6090af2e4d35a069e3aba/93ccb/local-llm-evolution.webp 600w,
/static/ad52f69610f6090af2e4d35a069e3aba/f15bd/local-llm-evolution.webp 1056w&quot; sizes=&quot;(max-width: 1056px) 100vw, 1056px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/ad52f69610f6090af2e4d35a069e3aba/37b9b/local-llm-evolution.png 300w,
/static/ad52f69610f6090af2e4d35a069e3aba/ab3c7/local-llm-evolution.png 600w,
/static/ad52f69610f6090af2e4d35a069e3aba/813c5/local-llm-evolution.png 1056w&quot; sizes=&quot;(max-width: 1056px) 100vw, 1056px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/ad52f69610f6090af2e4d35a069e3aba/813c5/local-llm-evolution.png&quot; alt=&quot;[Recent break throughs that made mobile inference possible](https://www.youtube.com/watch?v=o1wY5vnNVCc)&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=o1wY5vnNVCc&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Recent break throughs that made mobile inference possible&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;figure style=&quot;text-align:center; width:80%; margin: 0 auto;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1036px; &quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/419309f8a6127e4cd9d95b9b6ac63ec9/de8b8/key-device-cloud-metrics.png&quot; style=&quot;display: block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 44.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABU0lEQVR42nWQ6WoCQRCE911zSJJfUZOXMm8QHyCiSxRvRV3v20URRUEr+7VMkGAGiump6a6ubu98Put0OonbxdeAuz63OMdzvM1mo/F4rH6/r16vp8lkovl8blgul2o0Gspms8r5vjKZjOW12219RbEfcfC5nG/84XCQF4ahFouFRqORut2uifAG2+1WqdSHnp5f9Pb2rsdYTN/5vD7Tad3dPygZcfFEUq/xhEqlko7H48UhHVutlobDoQk5h7hdrVaazWYKOh2FUeycw3cijunQgN/tdhdBxqpUKqpWqxaT5NZAExoWCgWbYDAYGIdYsVhUrVb7zd/v9/LW67UVODGScQZYA3uFp7Ber1sT/hCnBm46nRpnguyw2WyqXC5bEuM5QcQBPIKI4RA33NRghhry2LmHCxwAPhnF4fpN078cNew+CAK72aMJuh0Q/4db/7e4H6AUjf0YzC6bAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/419309f8a6127e4cd9d95b9b6ac63ec9/577d7/key-device-cloud-metrics.webp 300w,
/static/419309f8a6127e4cd9d95b9b6ac63ec9/93ccb/key-device-cloud-metrics.webp 600w,
/static/419309f8a6127e4cd9d95b9b6ac63ec9/16209/key-device-cloud-metrics.webp 1036w&quot; sizes=&quot;(max-width: 1036px) 100vw, 1036px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/419309f8a6127e4cd9d95b9b6ac63ec9/37b9b/key-device-cloud-metrics.png 300w,
/static/419309f8a6127e4cd9d95b9b6ac63ec9/ab3c7/key-device-cloud-metrics.png 600w,
/static/419309f8a6127e4cd9d95b9b6ac63ec9/de8b8/key-device-cloud-metrics.png 1036w&quot; sizes=&quot;(max-width: 1036px) 100vw, 1036px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/419309f8a6127e4cd9d95b9b6ac63ec9/de8b8/key-device-cloud-metrics.png&quot; alt=&quot;Mobile = ~Cloud/10&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Mobile = ~Cloud/10&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;Mobile hardware constraints are about 2 orders of magnitude from the workstations or the cloud and judging from the data above it starts looking clear that it’s a matter of time before local models are just as capable as the current generation of cloud models.&lt;/p&gt;
&lt;p&gt;Look, I’m &lt;strong&gt;not making any claims about AGI or AI improving exponentially forever&lt;/strong&gt;. I’m just pointing out that what we can do in the cloud now, will be feasible on devices soon, which I think it’s reasonable and backed by the data.&lt;/p&gt;
&lt;h1&gt;Building for the future&lt;/h1&gt;
&lt;p&gt;Now, if you’re convinced that all of this makes sense and you’re interested in building something what to do now? Should you get a new device just for that?&lt;/p&gt;
&lt;p&gt;Well, I don’t think so. At least not yet.
The AIEdge SDK is still experimental and supported on just a few devices, so I recommend another way of going about this.&lt;/p&gt;
&lt;p&gt;In my opinion, at least so far, Google has adopted what it looks like a very coherent approach for their AI dev tools.
The APIs are very similar and there’s options for gradually going up the ladder of investment. Just look how similar they are.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI Edge SDK&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;google&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ai&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;edge&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;aicore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GenerativeModel
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;google&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ai&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;edge&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;aicore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generationConfig

&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; model &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GenerativeModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    generationConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; generationConfig &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; applicationContext
        maxOutputTokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;600&lt;/span&gt;
        temperature &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.9f&lt;/span&gt;
        topK &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;
        topP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1f&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateContentStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;buildPrompt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Google Generative AI SDK&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;google&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ai&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generativeai&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GenerativeModel
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;google&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ai&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generativeai&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generationConfig

&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; generativeModel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GenerativeModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    modelName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gemini-flash-1.5&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    apiKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BuildConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GEMINI_API_KEY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    generationConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; generationConfig &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        maxOutputTokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;600&lt;/span&gt;
        temperature &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.9f&lt;/span&gt;
        topK &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;
        topP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1f&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateContentStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;buildPrompt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Firebase with Vertex AI&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;google&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firebase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;vertexai&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GenerativeModel
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; com&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;google&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firebase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;vertexai&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generationConfig

&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; model &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Firebase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;vertexAI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generativeModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    modelName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gemini-1.5-flash&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    generationConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; generationConfig &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        maxOutputTokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;600&lt;/span&gt;
        temperature &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.9f&lt;/span&gt;
        topK &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;
        topP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1f&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;buildPrompt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Moving from one to another is almost as easy as changing the imports.&lt;/p&gt;
&lt;p&gt;So I think a &lt;strong&gt;good game plan&lt;/strong&gt; would be to validate your AI feature idea using &lt;a href=&quot;https://aistudio.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;AI Studio&lt;/a&gt;. If that works, then you can prototype it with the &lt;a href=&quot;https://github.com/google-gemini/generative-ai-android&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Generative AI SDK&lt;/a&gt;. If you’re confident that it can be shipped, roll it out to your users (probably A/B testing) using &lt;a href=&quot;https://firebase.google.com/docs/vertex-ai/get-started?platform=android&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Firebase with Vertex AI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you get positive feedback and you think price will be a problem, or rather, if the AI input data is privacy-sensitive, then you can try your chances with the &lt;a href=&quot;https://developer.android.com/ai/gemini-nano&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;AIEdge SDK&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Wrapping up&lt;/h1&gt;
&lt;p&gt;The article stretched out more than planned, but if you’re still here, you’re either really into this or just jumped to the conclusion. In any case, the &lt;strong&gt;bottomline&lt;/strong&gt; is that rate of improvement is significant enough that &lt;strong&gt;local models&lt;/strong&gt; could be &lt;strong&gt;12-18 months away&lt;/strong&gt; from being capable of handling many use cases locally. You can start thinking about it or even start building now.&lt;/p&gt;
&lt;p&gt;If you’re interested, you can read this three-part series on &lt;a href=&quot;../prototyping-an-ai-app-part-1/&quot;&gt;Prototyping an AI App&lt;/a&gt;, where I documented my process.&lt;/p&gt;
&lt;p&gt;We’re not there yet, but in my opinion this is a preview of what will likely become a standard capability of future apps. Just like GPS, Bluetooth, or NFC, many apps will build on this. Not every app will need it, just as not all apps require GPS or Bluetooth, but certainly, many will. You can read some more thoughts about that&lt;/p&gt;
&lt;h1&gt;What’s next?&lt;/h1&gt;
&lt;p&gt;There’s yet another SDK for LLM inference on Android that, as pointed out by &lt;a href=&quot;http://disq.us/p/30t7u72&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Paul&lt;/a&gt;, I have not talked about. It’s the &lt;a href=&quot;https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MediaPipe LLM Inference API&lt;/a&gt; and hope to play with that in the near future.&lt;/p&gt;
&lt;p&gt;For now, I want to get some hands-on experience with LoRA and how to apply to Gemini Nano. So you can expect an article on that soon.&lt;/p&gt;
&lt;p&gt;Besides that, earlier this year I’ve pushed a container to GCP with a Python backend and a very basic RAG implementation using &lt;a href=&quot;https://cloud.google.com/vertex-ai?hl=en&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Vertex AI&lt;/a&gt; and &lt;a href=&quot;https://www.langchain.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;LangChain&lt;/a&gt;. Now that &lt;a href=&quot;https://developers.googleblog.com/en/new-features-for-the-gemini-api-and-google-ai-studio/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Gemini 1.5&lt;/a&gt; is out, I need to try it with the new 1MI or 2MI tokens context window options and eventually hook it up with my AudioGuide AI App I built last year. Let’s see if can put those together.&lt;/p&gt;
&lt;p&gt;I’ll keep you posted either way. Cheers!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Gemini on Android, The story so far]]></title><description><![CDATA[Here we go again Right. Another article about AI. I’m sure you’ve read enough about it so far, but chances are that if you landed here, you…]]></description><link>https://iurysouza.dev/gemini-on-android/</link><guid isPermaLink="false">https://iurysouza.dev/gemini-on-android/</guid><pubDate>Thu, 17 Oct 2024 17:39:24 GMT</pubDate><content:encoded>&lt;h1&gt;Here we go again&lt;/h1&gt;
&lt;p&gt;Right. Another article about AI. I’m sure you’ve read enough about it so far, but chances are that if you landed here, you have at least some vague interest in it. I, for one, am very interested on the tech and with the direction this is going, especially with what has just started to roll out. FYI: if I say AI in this article, I probably mean &lt;a href=&quot;https://en.wikipedia.org/wiki/Large_language_model&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;LLM&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;On-Device LLMs are here!&lt;/h1&gt;
&lt;p&gt;Google just &lt;a href=&quot;https://android-developers.googleblog.com/2024/10/gemini-nano-experimental-access-available-on-android.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;announced&lt;/a&gt; their Android AICore beta program, and with it, we finally got access to Gemini Nano with AI Edge SDK via AICore. Did Android smartphones just got smarter? Well.. kinda.&lt;/p&gt;
&lt;figure style=&quot;text-align:center;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 559px; &quot;&gt;
      &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 122.33333333333334%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAYAAAD6S912AAAACXBIWXMAAAsTAAALEwEAmpwYAAAChUlEQVR42rWVTW9SURCG+RnGuNCFGzf+KjXGrXGn0bjQn6LRle61qUtISPiGtkCBUqAUKN/3ci+vPJOeC5WqpNETJjN3zsx75sw79xLTxlrq6loul3+U61ZM/3jFNqurd0O1mr4Urn5hqPPzc00mEw0GA3W7XV1cXJju9Xoms9lsG9BV3hkulSgHajd8TXqB/MBXKpVSuVxWtVpVsVjU4eGharWaPePngG3AS2PsSclKoEbV02ISKgiDVcLQKur3+1bpaDTa/cosf7GU5203+0aksP83Vndh3ADpy+npaQTsqroRy7AJGKAwR6OHw6Gm06np8XgcCX1E8KPx0VdsV0DM931ls1nF43FlMhljNp/P6+joyFiFXezj42MdHByoUqkol8tZTKFQsHh8UYVBEFhwu922KtkslUrm4xkwxqRer5vGzyGMDQcST4VXANkggOBms2mAJAOIDwGQZDSVEe9mdAuQKgjmZJI5AAEYwU8iPq7NdfGRQy4tWCwWa0CCGo2GBbhq8fHKcTqDTfMRiMOPDYHYyBVA+tfpdOzdxSYJPZ/PBWkEoz3PM+38vx0bekLpgJycnJicnZ2ZbrVakY9b8MxN1nP7y2ADSDJkAEgg7y82Fbuq8XMLANHEY19boZstNw6OIDePbhYhJp1OWyxkuSngAFdtNDaQABiJbpDpmRPXQ4Qe8i3Eh978Ltq77IIBd0EkbX58g9XpYSS6lLX9//8CrluOvLkf6MXXoh59yOjZ55yefsrp8ces2U9W+vmXoiZesDvgaLbQw/c/dOfVd91/u6+7r/d06+U33Xuzp9sr/eDdvvpTf/crw16hNVSi3leyMVCyPoh0otZf7Y2snwD+BDGzIKg74r/9AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/0cba80e463bffc7fe1088c011170e100/577d7/ai-core-beta-crop.webp 300w,
/static/0cba80e463bffc7fe1088c011170e100/0e12f/ai-core-beta-crop.webp 559w&quot; sizes=&quot;(max-width: 559px) 100vw, 559px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/0cba80e463bffc7fe1088c011170e100/37b9b/ai-core-beta-crop.png 300w,
/static/0cba80e463bffc7fe1088c011170e100/d2d21/ai-core-beta-crop.png 559w&quot; sizes=&quot;(max-width: 559px) 100vw, 559px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/0cba80e463bffc7fe1088c011170e100/d2d21/ai-core-beta-crop.png&quot; alt=&quot;AICore Beta Program&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;AICore Beta Program&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;Now, honestly, this is nothing short of amazing. The thing is that we’re on 2024, and so overwhelmed with information that it is sometimes hard to appreciate just how incredible this last paragraph reads. Especially if you’re sceptic (and rightfully so) about all the AI bros selling snake oil out there, but I digress.&lt;/p&gt;
&lt;p&gt;It was just in November 2022 that OpenAI released ChatGPT-3.5, the first version to become wildly popular, and here we are now, less than two years later, with a somewhat comparable model running &lt;strong&gt;ON DEVICE!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But how did we get here?&lt;/p&gt;
&lt;h1&gt;Android 🤝 LLMs - Quick Recap&lt;/h1&gt;
&lt;figure style=&quot;text-align:center;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 55.666666666666664%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAUICf/EABYBAQEBAAAAAAAAAAAAAAAAAAQDBf/aAAwDAQACEAMQAAABgBdvSjmaRSnzIb//xAAcEAACAgIDAAAAAAAAAAAAAAAEBQIGAAMBEhP/2gAIAQEAAQUCf1ivV14KhSGNQ6Rcl64jzmQRu6zK2cj5/8QAIhEBAAIBAwMFAAAAAAAAAAAAAQIRAwATMQRBURIhUmFx/9oACAEDAQE/Ad3A5KvMRZfCDKr87gL9+kL967aXpe2OVUc0vBykoi+Uifmv/8QAIhEAAgECBQUAAAAAAAAAAAAAAQIRAyEABBIiQRMyNFFx/9oACAECAQE/ASM0tECKRdad36pBL6V3eNpF5btieIwEz0XrUWMncyLqN+YpAT8AHrH/xAAmEAABAwQBBAEFAAAAAAAAAAADAQIEBRESIRMABxQiYQYjJDFi/9oACAEBAAY/ApdDgqWbCiKDil1aDHZUi+RHEd3MDAO2kK4Yks3IbWLu+4dKkCUEeXMBClFDTwCqMYZzIIrgAXm/JGxcxtci/csisW26bT+3HcI9P+locVw4wqvTYRpZJBZcqVJOjw05E4SvkXFdXrb9PVmFnEJGhFIuNymgwzFXFvrcpQPIuOsfb1RERNdMI0EJCKZLkSnweTaLdeTx88v6vf56AADRCCKMMYxsABGsYzJjGtTj0jWojU+E6//EABsQAQADAQEBAQAAAAAAAAAAAAEAESFBMVGB/9oACAEBAAE/ITp15hC8TGlNrQDG9ziUvfAoiDUxf4XROCtKiIHkBhscAAssBAR6g2W36RP17qLpa9bBKC7wDk//2gAMAwEAAgADAAAAEGD/AP/EABkRAQEAAwEAAAAAAAAAAAAAAAERACExQf/aAAgBAwEBPxAkESCLOCYnSOAJion0Z9qtAigVaCBn/8QAFxEBAQEBAAAAAAAAAAAAAAAAARExAP/aAAgBAgEBPxCJORzYGRUUCCAAnb0kVqtF1BsBUASf/8QAGRABAAMBAQAAAAAAAAAAAAAAAQARIRAx/9oACAEBAAE/EBRXUOr6LpspEAtOYkOvPWFk81cELIx49X9d6sHmSLAlmEi9QJuv9ScgpJAABlO2RT+1FHD+Cf/Z&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/b2748a5c12926680016334b7cf71423d/577d7/ai-at-the-core.webp 300w,
/static/b2748a5c12926680016334b7cf71423d/93ccb/ai-at-the-core.webp 600w,
/static/b2748a5c12926680016334b7cf71423d/1aa16/ai-at-the-core.webp 1200w,
/static/b2748a5c12926680016334b7cf71423d/daa23/ai-at-the-core.webp 1800w,
/static/b2748a5c12926680016334b7cf71423d/74508/ai-at-the-core.webp 1928w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/b2748a5c12926680016334b7cf71423d/622f1/ai-at-the-core.jpg 300w,
/static/b2748a5c12926680016334b7cf71423d/58041/ai-at-the-core.jpg 600w,
/static/b2748a5c12926680016334b7cf71423d/0516a/ai-at-the-core.jpg 1200w,
/static/b2748a5c12926680016334b7cf71423d/3bb1d/ai-at-the-core.jpg 1800w,
/static/b2748a5c12926680016334b7cf71423d/904e7/ai-at-the-core.jpg 1928w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/jpeg&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/b2748a5c12926680016334b7cf71423d/0516a/ai-at-the-core.jpg&quot; alt=&quot;AI at the core&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;AI at the core&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;This story started back in 2023 with the Pixel 8 lineup, the first smartphones to ship with &lt;a href=&quot;https://blog.google/products/pixel/google-tensor-g3-pixel-8/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Google Tensor&lt;/a&gt; processors that were good enough for LLM inference.&lt;/p&gt;
&lt;p&gt;This enabled the devices to run LLMs locally, though initially limited to just a handful of apps and features, like smart replies and the recorder transcript summarization. There was a program that you could enroll to get access to Gemini Nano, but that was kinda restrictive. So no open access yet.&lt;/p&gt;
&lt;figure style=&quot;text-align:center;&quot;&gt;
    &lt;img src=&quot;/a3195d2670b5ad5587ef158768647e8c/pixel-8-summary.webp&quot; alt=&quot;Transcript Summarization&quot;&gt;
&lt;/figure&gt;
&lt;/br&gt;
&lt;figure style=&quot;text-align:center;&quot;&gt;
  &lt;img src=&quot;/60406d1c83c25e3c44113da747a9d0b6/pixel-voice.webp&quot; alt=&quot;Pixel 8 Improved Voice Model&quot;&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;But of course, on-device generative AI is really complex: 150 times more complex than the most complex model on Pixel 7 just a year ago. Tensor G3 is up for the task, with its efficient architecture co-designed with Google Research.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.google/products/pixel/google-tensor-g3-pixel-8/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Generative AI SDK&lt;/h1&gt;
&lt;p&gt;A few months later, around December last year, Google started gearing up and stealthily releasing the &lt;a href=&quot;https://github.com/google-gemini/generative-ai-android&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Generative AI SDK&lt;/a&gt;, which made it easier to play with the Gemini API. This one was &lt;strong&gt;not running on device&lt;/strong&gt;, though.&lt;/p&gt;
&lt;p&gt;It was still very limited, and clearly not a lot of resources were put into it. Heck, it was not really announced anywhere. Besides, it was still blocked in a lot of countries (Europe) by that time.&lt;/p&gt;
&lt;div style=&quot;width: 80%; margin: 0 auto; text-align: center;&quot;&gt;
        &lt;blockquote class=&quot;twitter-tweet&quot; style=&quot;text-align: center;&gt;
            &lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I remember using a VPN to access Bard early on, but it was a one-time thing.&lt;br&gt;&lt;br&gt;Now, I suppose I&amp;#39;ll have to wait until it&amp;#39;s available in the EU. I wonder how OpenAI managed to push generative AI apis here while Google is still restricted. &lt;a href=&quot;https://t.co/yzHV6rIrve&quot;&gt;https://t.co/yzHV6rIrve&lt;/a&gt; &lt;a href=&quot;https://t.co/5dzuRrXpfv&quot;&gt;pic.twitter.com/5dzuRrXpfv&lt;/a&gt;&lt;/p&gt;&amp;mdash; iury souza (@IurySza) &lt;a href=&quot;https://twitter.com/IurySza/status/1740811348303606254?ref_src=twsrc%5Etfw&quot;&gt;December 29, 2023&lt;/a&gt;
        &lt;/blockquote&gt; 
        &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;/br&gt;
&lt;p&gt;That happened right around the time I was building my first Gen AI app. You can read more about that here: &lt;a href=&quot;../prototyping-an-ai-app-part-1/&quot;&gt;Building an AI Audio Guide - Part 1&lt;/a&gt;
. I was mainly targeting OpenAI’s API back then, but I was able easily hook up Gemini using that SDK. Quality was still not there yet, but it worked.&lt;/p&gt;
&lt;h1&gt;Vertex AI for Firebase&lt;/h1&gt;
&lt;p&gt;Fast forward another six months, and we’re at Google I/O Connect 2024. Now, a joint venture with &lt;a href=&quot;https://cloud.google.com/vertex-ai?hl=en&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Vertex AI&lt;/a&gt; and &lt;a href=&quot;https://firebase.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Firebase&lt;/a&gt; was announced.
Vertex AI is Google’s Cloud solution for AI products and now, app clients could tap into it without having to deal with GCP.&lt;/p&gt;
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 56.25%; position: relative; height: 0; overflow: hidden; margin-bottom: 1rem&quot; &gt; &lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/sfWczhmnA3s?si=IbVm30tznkriBw4m&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot; style=&quot; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot;&gt;&lt;/iframe&gt; &lt;/div&gt;
&lt;p&gt;What I got from this was that while the Generative AI SDK was OK for prototyping, Firebase Gen AI was meant to be a secure and more robust channel for production use.&lt;/p&gt;
&lt;h1&gt;Any Local LLMs Yet?&lt;/h1&gt;
&lt;p&gt;During that same &lt;em&gt;Connect&lt;/em&gt; event, Google announced that Android would soon open up access to core APIs for on-device LLM inference. Some architecture diagrams were shown about this new AI Core thing. But still nothing for us devs to play with.&lt;/p&gt;
&lt;p&gt;Then, a couple of months later, the Pixel 9 family was launched with more Gen AI stuff and a slightly better Chip in terms of raw performance, but with a new TPU (Tensor Processing Unit) for faster LLM inference, &lt;a href=&quot;https://9to5google.com/2024/08/19/google-tensor-g4-pixel-9-interview/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;reportedly&lt;/a&gt; at 45 tokens (which is pretty decent) per second now. Also, it’s interesting to notice that these devices shipped with min 16GB RAM out of which &lt;a href=&quot;https://www.androidauthority.com/tested-pixel-9-pro-ai-ram-3472624/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;3GB reserved&lt;/a&gt; just for LLM inference.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pro models lock 3GB RAM exclusively for AICore and the Tensor’s TPU.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, you read that right. Almost 20% of device memory reserved for AI. Also, you should expect multiple GBs of model data stored locally (more about that later). This should not be taken lightly. They mean business!&lt;/p&gt;
&lt;p&gt;Ok, so &lt;em&gt;finally&lt;/em&gt;, on October 1st, &lt;a href=&quot;https://developer.android.com/ai/gemini-nano/experimental&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;strong&gt;Gemini Nano with Google AI Edge SDK&lt;/strong&gt;&lt;/a&gt; was open for us devs 🎉. Well, at least for &lt;em&gt;beta&lt;/em&gt; testing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Gemini Nano experimental access is designed for developers seeking to test enhancement of their apps with cutting-edge on-device AI capabilities.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Gemini Nano&lt;/h1&gt;
&lt;p&gt;The Gemini Nano model is specifically optimized for on-device use, which means that it can runs completely locally.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://deepmind.google/technologies/gemini/nano/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Nano&lt;/a&gt; is the smallest of the Gemini lineup, with &lt;a href=&quot;https://deepmind.google/technologies/gemini/flash/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Flash&lt;/a&gt;, &lt;a href=&quot;https://deepmind.google/technologies/gemini/pro/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Pro&lt;/a&gt; and &lt;a href=&quot;https://deepmind.google/technologies/gemini/ultra/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Ultra&lt;/a&gt; being the other ones. It is the most efficient version and because of that it needs to be much smaller, with Nano-1 and Nano-2 models being only 1.8B and 3.25B parameters, respectively. For comparison, Gemini 1.5 Pro has 1.5 trillion - with a T (note that these things don’t exactly scale linearly though). Due to this size difference it had to make some compromises, but still managed to perform remarkably well.&lt;/p&gt;
&lt;p&gt;How? Quoting straight from the &lt;a href=&quot;https://arxiv.org/pdf/2312.11805#page=20.45&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Gemini paper&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The power of Nano lies in its training process, which involves “distilling” knowledge from the larger Gemini models. This approach allows Nano to deliver substantial computational power in a smaller form factor, making AI more personal and accessible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This model is not as general as the other ones. Being smaller, is better suited for some specific tasks, also quoting the paper:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;These models excel in summarization and reading comprehension tasks with per-task fine-tuning.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can see these features in action on some of the new Pixel apps like the &lt;a href=&quot;https://www.androidpolice.com/google-new-ai-weather-summaries/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;AI Weather Report&lt;/a&gt; of the new Pixel Weather App and the &lt;a href=&quot;https://store.google.com/intl/en/ideas/articles/pixel-screenshots/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Pixel Screenshots&lt;/a&gt; app.&lt;/p&gt;
&lt;p&gt;To get started with it you have to enrol on the beta program. This will enable you to update the AICore app which comes installed on the Pixel 8 and 9. After that you can start using the SDK.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: I’m purposefully skipping the &lt;a href=&quot;https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MediaPipe LLM&lt;/a&gt; Inference API, released early 2024, because using it is more involved and requires us to manually push models (non Gemini) to the device. I’ll try to revisit it later.&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;What’s next?&lt;/h1&gt;
&lt;p&gt;Welcome to the new normal. Some people loathe it, but this has been an open secret for years. This vision of a shift from a mobile-first to an AI-first world was &lt;a href=&quot;https://blog.google/technology/ai/making-ai-work-for-everyone/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;announced&lt;/a&gt; by Sundar himself back in 2017 and I can only see it accelerating.&lt;/p&gt;
&lt;figure style=&quot;text-align:center;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 28.333333333333332%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABHElEQVR42j1R7W7CMAzs+7/cEB1IlQYMmua7TZq0DAnam52O/bDOduzLnVyllDHNM1JKGMcRmfOckaepxJQTziZjd51Qtxwzdt8j9lyLO+pG4OPzgvqW8XotqDqpoJSGcx6SUGkNax0k9UUnqe/QXBTqk8Hx4lB/aco19oQHqg/NFfvjGSeV8HwWQgljLJz30MYQkYbWZiOlD7jnnIV3Br130LKj3CIOfcGe9oZhKG+PxwNV2wpasoWEl29CkErzr9hYW2Ijt2BH3Z/64o5mVdm1ZPmFSioFS7beNpn0TVCGGdWmmtH7HiFGhBBJWSjBue97/LBCLvgwHEMIiHSYGEcaCnSkhEx9rkc6WiQitrWuK5ZlwUK45RsCK34BdLHCM/9/duQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/921d4e86a85576fd1d22cd8c174df492/577d7/shift-mobile-first-ai-first.webp 300w,
/static/921d4e86a85576fd1d22cd8c174df492/93ccb/shift-mobile-first-ai-first.webp 600w,
/static/921d4e86a85576fd1d22cd8c174df492/1aa16/shift-mobile-first-ai-first.webp 1200w,
/static/921d4e86a85576fd1d22cd8c174df492/3f63b/shift-mobile-first-ai-first.webp 1519w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/921d4e86a85576fd1d22cd8c174df492/37b9b/shift-mobile-first-ai-first.png 300w,
/static/921d4e86a85576fd1d22cd8c174df492/ab3c7/shift-mobile-first-ai-first.png 600w,
/static/921d4e86a85576fd1d22cd8c174df492/5c837/shift-mobile-first-ai-first.png 1200w,
/static/921d4e86a85576fd1d22cd8c174df492/8e714/shift-mobile-first-ai-first.png 1519w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/921d4e86a85576fd1d22cd8c174df492/5c837/shift-mobile-first-ai-first.png&quot; alt=&quot;The Big Shift&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;The Big Shift&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;I could write another article just about this vision and what it means for mobile dev, especially us Android Developers, which for years were used to be in the very center of everything Google announced (remember IO 2018?) and now are left feeling a bit alienated by these changes. But let’s do that another time. For now, I’ll just say that mobile is still the platform.
AI will be just another tool in our belt.&lt;/p&gt;
&lt;div style=&quot;width: 80%; margin: 0 auto; text-align: center;&quot;&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Welp... you know what&amp;#39;s up tomorrow.&lt;br&gt;A erm... — a SHIT TON of AI is about to drop!&lt;br&gt;&lt;br&gt;This quote is from a 2017 article from Sundar, I but thnk they&amp;#39;re now positioned to make that happen. Don&amp;#39;t worry, mobile isnt going anywhere, I just thnk that they&amp;#39;re about to dial up some knobs &lt;a href=&quot;https://t.co/oJRy1MtZ0Y&quot;&gt;https://t.co/oJRy1MtZ0Y&lt;/a&gt; &lt;a href=&quot;https://t.co/qqSiln5pZJ&quot;&gt;pic.twitter.com/qqSiln5pZJ&lt;/a&gt;&lt;/p&gt;&amp;mdash; iury souza (@IurySza) &lt;a href=&quot;https://twitter.com/IurySza/status/1790058110310039906?ref_src=twsrc%5Etfw&quot;&gt;May 13, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;/br&gt;
&lt;p&gt;This was a just a short recap of how we got here, I’m trying to keep these in a shorter format, so I’ll have to wrap this up.
But if you’re interested in learning more, like and subscribe 😛.&lt;/p&gt;
&lt;p&gt;I’m currently playing with the Nano model on the Pixel 9 and hope to put together some of my notes into another article about my first impressions and some ideas of how you can try implementing LLM based features on &lt;strong&gt;any&lt;/strong&gt; device.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Kotlin DEVEX is not great, but Amper could fix it.]]></title><description><![CDATA[I like working with kotlin. Great language, great features, super nice community, and java interop can be a superpower. That said, Kotlin…]]></description><link>https://iurysouza.dev/kotlin-devex-is-not-great-amper-could-fix-it/</link><guid isPermaLink="false">https://iurysouza.dev/kotlin-devex-is-not-great-amper-could-fix-it/</guid><pubDate>Thu, 13 Jun 2024 20:05:37 GMT</pubDate><content:encoded>&lt;p&gt;I like working with kotlin. Great language, great features, super nice community, and java interop can be a superpower.&lt;/p&gt;
&lt;p&gt;That said, Kotlin Developer Experience (DEVEX) gives me mixed feelings. On the one hand you have Jetbrains, the maker of
arguably the best suite of IDEs in the market behind it. This automatically gives us, kotlin devs great support for language features
directly in our editor. Debugging and refactoring Kotlin code is a breeze comparing to most other languages.&lt;/p&gt;
&lt;p&gt;Now, the same cannot be said about other parts of the typical day-to-day workflow.
For example, say you want to build a tiny Kotlin library targeting the JVM and distribute it for other devs to use.
That’s a standard workflow that in the &lt;em&gt;year of our lord, 2024&lt;/em&gt;, should be more than straight forward, right?
Let’s take a look.&lt;/p&gt;
&lt;h1&gt;Getting Started&lt;/h1&gt;
&lt;p&gt;Ok. How would you go about doing that?
Well, on Kotlin’s &lt;a href=&quot;https://kotlinlang.org/docs/getting-started.html#install-kotlin&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Getting Started&lt;/a&gt; page, Jetbrains
states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;h2&gt;Install Kotlin&lt;/h2&gt;
&lt;p&gt;Kotlin is included in each &lt;a href=&quot;https://www.jetbrains.com/idea/download/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;IntelliJ IDEA&lt;/a&gt;
and &lt;a href=&quot;https://developer.android.com/studio&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Android Studio&lt;/a&gt; release. Download and install one of these IDEs to start
using Kotlin.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fair enough, we’re going to use IntelliJ anyway, so yeah.
Fire up your IntellijIDEA and use the &lt;em&gt;New Project&lt;/em&gt; menu.
&lt;img src=&quot;https://i.imgur.com/FDuCDhc.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;That works, but IMO, far from ideal. Not a big fan of clicking through a GUI just for this. What I prefer instead is
using a CLI tool. Luckily, Gradle helps with that.
Being the most common build tool for kotlin projects these days, I think fine to have it handle this part
of the flow. This requires you to have Gradle installed globally on your system, which you can do by
using &lt;a href=&quot;https://sdkman.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;sdkman&lt;/a&gt;.
This is what it looks like to create a new kotlin library project using the Gradle CLI:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ gradle init
Starting a Gradle Daemon &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subsequent builds will be faster&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

Select &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; of build to generate:
  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;: Application
  &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;: Library
  &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;: Gradle plugin
  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;: Basic &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;build structure only&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
Enter selection &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;default: Application&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;

Select implementation language:
  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;: Java
  &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;: Kotlin
  &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;: Groovy
  &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;: Scala
  &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;: C++
  &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;: Swift
Enter selection &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;default: Java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;

Enter target Java version &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;min: &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;, default: &lt;span class=&quot;token number&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;: &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;

Project name &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;default: gradle-kotlin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;:

Select build script DSL:
  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;: Kotlin
  &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;: Groovy
Enter selection &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;default: Kotlin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;

Generate build using new APIs and behavior &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;some features may change &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; the next minor release&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;? &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;default: no&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;yes, no&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;yes&lt;/span&gt;


&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; Task :init
To learn &lt;span class=&quot;token function&quot;&gt;more&lt;/span&gt; about Gradle by exploring our Samples at https://docs.gradle.org/8.7/samples/sample_building_kotlin_libraries.html

BUILD SUCCESSFUL &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; 37s
&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; actionable task: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; executed&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is what you get:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ tree &lt;span class=&quot;token parameter variable&quot;&gt;-L&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── lib
│   ├── build.gradle.kts
│   └── src
│       ├── main
│       └── &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;
└── settings.gradle.kts&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok, this is kinda nice. There is a way to get started via CLI, like all the other cool kids do. Check ✅.&lt;/p&gt;
&lt;h1&gt;Adding a dependency&lt;/h1&gt;
&lt;p&gt;Now, what if you want to add a new dependency? How do you go about that?&lt;/p&gt;
&lt;p&gt;Say for example, I need to use &lt;code class=&quot;language-text&quot;&gt;okhttp&lt;/code&gt; for creating a small HTTP client. How do I do it?
First of all, we need to check which version are we going to use and what are its maven coordinates.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;You can find out by navigating to maven and searching for &lt;a href=&quot;https://mvnrepository.com/search?q=okhttp&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;okhttp&lt;/a&gt;. Click
through the UI and find out the version you want to use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then you would navigate to the &lt;code class=&quot;language-text&quot;&gt;build.gradle.kts&lt;/code&gt; file in your project and add this snippet to it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.squareup.okhttp3:okhttp:4.12.0&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Then, you would sync your project, by running &lt;code class=&quot;language-text&quot;&gt;./gradlew&lt;/code&gt; or clicking through the GUI and you would be able to use
the library.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Done! Added a dependency.
And now you’re ready to code your small client application.
This felt a bit convoluted though. If you’re a beginner you probably won’t even know what &lt;code class=&quot;language-text&quot;&gt;implementation&lt;/code&gt; stands for.
What about that coordinate? You’re not gonna memorise them, are you?&lt;/p&gt;
&lt;p&gt;Now, let’s stop right there and go the other side.&lt;/p&gt;
&lt;h2&gt;The Python way&lt;/h2&gt;
&lt;p&gt;Let’s contrast that to Python when using &lt;code class=&quot;language-text&quot;&gt;Poetry&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you’re not familiar with it, &lt;a href=&quot;https://python-poetry.org/docs/basic-usage/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Poetry&lt;/a&gt; is a dependency management and
packaging tool for Python.
It provides a standardised way to manage Python projects, including: environment and dependency management, lock files,
packaging and more.&lt;/p&gt;
&lt;h3&gt;Getting Started and Adding a dependency&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ poetry new pytho_demo &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; python_demo
Created package python_demo &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; python_demo&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ poetry &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; flask
Creating virtualenv python-demo &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; /Users/iury.souza/projects/python-demo/.venv
Using version ^3.0.3 &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; flask

Updating dependencies
Resolving dependencies&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;.3s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

Package operations: &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; installs, &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; updates, &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; removals

  - Installing markupsafe &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2.1&lt;/span&gt;.5&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  - Installing blinker &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.8&lt;/span&gt;.2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  - Installing click &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8.1&lt;/span&gt;.7&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  - Installing itsdangerous &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2.2&lt;/span&gt;.0&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  - Installing jinja2 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3.1&lt;/span&gt;.4&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  - Installing werkzeug &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3.0&lt;/span&gt;.3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  - Installing flask &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3.0&lt;/span&gt;.3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

Writing lock &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You’ll get this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ tree &lt;span class=&quot;token parameter variable&quot;&gt;-L&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
├── README.md
├── poetry.lock
├── pyproject.toml
├── python_demo
│   └── __init__.py
└── tests
    └── __init__.py

&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; directories, &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; files&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Done. Much simpler, right?. You can immediately start coding.
This highlights a few key differences between the two development workflows and their developer experience:&lt;/p&gt;
&lt;h2&gt;What Poetry does better&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ease of Use:&lt;/strong&gt;
Python’s Poetry offers a straightforward, CLI-driven approach to managing dependencies. In contrast, Kotlin’s
reliance on manually configuring &lt;code class=&quot;language-text&quot;&gt;Gradle&lt;/code&gt; files is more involved and error-prone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Impact on Development Workflow:&lt;/strong&gt;
Python developers can rapidly add and manage dependencies without stepping out of their development flow. On the other
hand,
Kotlin developers often find themselves fiddling with build scripts and build syncs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reproducibility and Environment Management:&lt;/strong&gt;
Poetry’s management of &lt;code class=&quot;language-text&quot;&gt;pyproject.toml&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;poetry.lock&lt;/code&gt; files ensures that builds are reproducible and environments
are consistent across different machines and setups. With Kotlin there’s no such elegant solution.
You could use the gradle lock options, but that’s not a standard workflow and doesn’t actually work the same way.&lt;/p&gt;
&lt;h1&gt;Why a CLI approach is better&lt;/h1&gt;
&lt;p&gt;Using the CLI is a better approach for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; The CLI provides a consistent interface for developers to interact with the build system. This
reduces the cognitive load on developers and makes it easier to remember how to perform common tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automation:&lt;/strong&gt; The CLI can be easily automated using scripts or other tools. This allows developers to perform
repetitive tasks more quickly and efficiently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replicability:&lt;/strong&gt; The CLI commands can be easily shared with other developers, making it easier to replicate
workflows across different machines and environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;What’s all the fuss about?&lt;/h1&gt;
&lt;p&gt;You might be thinking:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;- Uh… I don’t see the big deal. I’m fine with the GUI. It’s just a few clicks.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Well, yeah. But consider this:
How many times do you add or remove dependencies from a project? Besides, I didn’t even touch other aspects like
testing,
or publishing Kotlin libraries as it would make this post too long, and would hit the same points. All of these are also
more convoluted than they should be and could benefit from a CLI based approach.&lt;/p&gt;
&lt;p&gt;Besides, a lot Kotlin (especially Android) get so rely on the IDE for managing their environment and end up not getting
familiar with a CLI based workflow. Missing all the benefits that come with it.&lt;/p&gt;
&lt;p&gt;An obvious counterargument is that Kotlin has many constraints that Python and other languages doesn’t have, like the
JVM, and the need to be
compatible with the whole Java ecosystem, but maybe there’s some tradeoffs people would be willing to make to have a
better DEVEX in at least certain use cases. I think that either the Kotlin or Gradle team could take a page from
Poetry’s book and work to make the development experience more streamlined and user-friendly.&lt;/p&gt;
&lt;h1&gt;Moving Forward&lt;/h1&gt;
&lt;p&gt;I think there’s a huge opportunity here for Kotlin to improve its developer experience by providing first-class
support for the command line for common
workflows. &lt;a href=&quot;https://www.jetbrains.com/help/kotlin-multiplatform-dev/amper.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Jetbrain’s Amper&lt;/a&gt; kinda looks
like a step in the right direction; I hope we devs can tap into that system via the CLI.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;If you&amp;#39;ve been paying attention, you might have noticed that both &lt;a href=&quot;https://twitter.com/gradle?ref_src=twsrc%5Etfw&quot;&gt;@gradle&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/jetbrains?ref_src=twsrc%5Etfw&quot;&gt;@jetbrains&lt;/a&gt; are working on transitioning from imperative to declarative approaches for configuring builds. ⚒️&lt;/p&gt;&amp;mdash; iury souza (@IurySza) &lt;a href=&quot;https://twitter.com/IurySza/status/1796566047870636437?ref_src=twsrc%5Etfw&quot;&gt;May 31, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;br&gt;
&lt;h2&gt;A Glimpse into a Better Kotlin DEVEX&lt;/h2&gt;
&lt;p&gt;What Poetry does for Python, Amper could do for Kotlin. When you run Poetry init, it generates a &lt;code class=&quot;language-text&quot;&gt;pyproject.toml&lt;/code&gt; file.
This is what a typical &lt;code class=&quot;language-text&quot;&gt;pyproject.toml&lt;/code&gt; file looks like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;toml&quot;&gt;&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;tool.poetry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;audioguide-ai-api&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.0.1&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The backend for the audioguide-ai project&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;authors&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Iury Souza &amp;lt;iurysza@gmail.com&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;readme&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;README.md&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;tool.poetry.dependencies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;python&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^3.12.2&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;Flask&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^3.0.3&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;python-dotenv&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^1.0.1&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;gunicorn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^22.0.0&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;build-system&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;requires&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;poetry-core&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;build-backend&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;poetry.core.masonry.api&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is a declarative way to configure your Python project.
You can either modify this file directly or use poetry commands to add or remove dependencies, etc.&lt;/p&gt;
&lt;p&gt;Now, this is an example of Amper’s module file, which is also declarative way to configure your project:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; lib
  &lt;span class=&quot;token key atrule&quot;&gt;platforms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;jvm&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; android&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; iosArm64&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; iosSimulatorArm64&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; iosX64&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# Shared Multiplatform dependencies:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;org.jetbrains.compose.foundation:foundation:1.5.0-rc01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; exported
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;org.jetbrains.compose.material3:material3:1.5.0-rc01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; exported

  &lt;span class=&quot;token comment&quot;&gt;# Android-only dependencies&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;dependencies@android&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;androidx.activity:activity-compose:1.7.2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; exported
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;androidx.appcompat:appcompat:1.6.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; exported

  &lt;span class=&quot;token comment&quot;&gt;# iOS-only dependencies with a dependency on a CocoaPod&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;dependencies@ios&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;pod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;FirebaseCore&apos;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;~&gt; 6.6&apos;&lt;/span&gt;

  &lt;span class=&quot;token key atrule&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;kotlin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;serialization&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; json

    &lt;span class=&quot;token comment&quot;&gt;# Enable Compose Multiplatform framework&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;compose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; enabled&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Imagine now, if you could run something like &lt;code class=&quot;language-text&quot;&gt;amper init&lt;/code&gt; and have a project scaffolded for you. You could then run:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ amper &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; okhttp &lt;span class=&quot;token parameter variable&quot;&gt;-android&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And have a new dependency added to your project, and a lockfile generated.
After that you could run:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ amper &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which would read from the lockfile, download the dependencies, and sync the project for you.
Maybe you would have your credentials in the &lt;code class=&quot;language-text&quot;&gt;module.yml&lt;/code&gt; file and you could run &lt;code class=&quot;language-text&quot;&gt;amper publish&lt;/code&gt; to publish your
library.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;These are just some thoughts I’ve put together. I think that Kotlin is a great language, but there’s a lot of room for
improvement in the developer experience. I think that embracing the CLI more fully could be a step in the right
direction.&lt;/p&gt;
&lt;p&gt;If you ever wrote a Node module, a Ruby gem or a Cargo package, you know how easy it is out there. I think it’s high
time we have that too.&lt;/p&gt;
&lt;br&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: I chose to compare Kotlin with Python just because I recently had to work with it and it was fresh in my mind,
but
the same could be said about other languages like Rust, Go, JS, Ruby and many others.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title><![CDATA[Sketch, refine, rinse and repeat]]></title><description><![CDATA[Let’s get to it! This is the final installment in my three-part series on prototyping an AI audio guide app. If you missed them, here’s…]]></description><link>https://iurysouza.dev/prototyping-an-ai-app-part-3/</link><guid isPermaLink="false">https://iurysouza.dev/prototyping-an-ai-app-part-3/</guid><pubDate>Sat, 20 Jan 2024 19:48:07 GMT</pubDate><content:encoded>&lt;p&gt;Let’s get to it!&lt;/p&gt;
&lt;p&gt;This is the final installment in my three-part series on prototyping an AI audio guide app. If you missed them, here’s
where it started: &lt;a href=&quot;../prototyping-an-ai-app-part-1/&quot;&gt;Part 1&lt;/a&gt; and &lt;a href=&quot;../prototyping-an-ai-app-part-2/&quot;&gt;Part 2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Following my mantra of fast iteration, I decided to just build the most basic thing first.
This will help you cut through the &lt;em&gt;writers block&lt;/em&gt; issue and get you into writing the code faster.
&lt;br/&gt;
That’s usually what we devs want to do, but it can result in poor decisions and harming your project from the start.
Because of that, I prefer a middle-ground solution that addresses both problems.
I call this the coding equivalent of &lt;em&gt;Sketch and Refine&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In art and design, ‘Sketch and Refine’ is a method where an initial sketch is refined over time to create a final
piece, improving precision and depth in the design process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is an example of that in practice. &lt;a href=&quot;https://www.youtube.com/watch?v=G-05XFKZP9o&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Source: Digital Art Timelapse&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;https://i.imgur.com/TyOVvlA.png&quot; width=&quot;200&quot;&gt;&lt;/td&gt;
&lt;td&gt;Initial outline for a semi-realistic digital painting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;https://i.imgur.com/YTO7yi2.png&quot; width=&quot;200&quot;&gt;&lt;/td&gt;
&lt;td&gt;Coloring and detailed features added.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;https://i.imgur.com/8Rpj25B.png&quot; width=&quot;200&quot;&gt;&lt;/td&gt;
&lt;td&gt;Well-defined portrait with attention to facial nuances.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;https://i.imgur.com/i5hWTho.png&quot; width=&quot;200&quot;&gt;&lt;/td&gt;
&lt;td&gt;Clothing, textures and hair refinement added.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;https://i.imgur.com/XqQeE8w.jpg&quot; width=&quot;200&quot;&gt;&lt;/td&gt;
&lt;td&gt;Final stage: last details, textured background&lt;br/&gt; and sunlight highlighting.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The idea is to iterate through multiple versions of the product rather than trying to get it right on the first try.
This approach enables collecting real-world feedback, making adjustments, validating assumptions, and gradually
improving the product.&lt;/p&gt;
&lt;p&gt;A similar concept, often credited to &lt;a href=&quot;https://en.wikipedia.org/wiki/Kent_Beck&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Kent Beck&lt;/a&gt;, has long been associated with
the &lt;a href=&quot;https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Unix Way&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Make It Work. Make It Right. Make It Fast.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, that’s the plan: to build a very basic prototype and keep iterating on it.&lt;/p&gt;
&lt;h1&gt;Basic Prototype&lt;/h1&gt;
&lt;p&gt;Having the “Sketch &amp;#x26; Refine” idea in mind, I decided to plan the basic app flow with three simple screens: Home Screen,
Preview Screen, and Audio Guide Screen. This is what it looked like:&lt;/p&gt;
&lt;figure style=&quot;text-align:center;&quot;&gt;
    &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 80%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAACAElEQVR42q1U2XabQAz1z/SYbQZm2Bk2Y4yd5TQnaf//V24lGRqndU77kIcBSUhXV0KaXZC0+Mqz44cfOzmh6bDpm8P/ypu+8+MaKkkRaoNvYQ5fWdItPF3AiyxCFcNTJKscUZyQLYWvS2hDb2WIhIO2mZwwcczQISJjWk1w4yPyZoZOOwKsKWsjAZyU5cgyi0ZklRI7sku85dOJfAW0I6rhCf38HfX4hKScydZD5weofKLMnbSD5cBcA6PsILK0inyjdKQEjkt2opiSmQ2UeUDmzpgf3jAurzDVjDgbEOcjEpIDAufADTD4DNDWJ2gKdMRwXN6Q5JzkKIDcbEUl6Y3hGvNPQGZX9g9U+gtsOSFrlpXhKD1SxIpbwQkYPLwHuGV7L+0AXTDYjPbwjOnyA1V/kQRcsnynxKY63Qe8ZRhLmRNsw2x7lN0Fx8efYmOd7SxzOyyx/xRQkZL+Acgyl8w/pnALySf5WQkDFuRH/lx+aFoBDEz/seS4OEopXC6Pjaa+aeqZqRdiekZFbNPmLKPEPy9efRSBufFZxs5/Z9hStlaGmadd3Qw2D/1eldhHJQW3YuNv7BOsA5/kA4r2dB1s3oIotrJ6nq7onZJuZPX2tHpBpH+vnqzhunqKfPzounoSz4e2SC4Hj1h663L7fy1++/FCuLlA/HuXw1dfX78ASiEDIhqNfkwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/f0b6d4f397259ec8c8e35662addddd42/577d7/ai_app_diagram.webp 300w,
/static/f0b6d4f397259ec8c8e35662addddd42/93ccb/ai_app_diagram.webp 600w,
/static/f0b6d4f397259ec8c8e35662addddd42/1aa16/ai_app_diagram.webp 1200w,
/static/f0b6d4f397259ec8c8e35662addddd42/5db19/ai_app_diagram.webp 1488w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/f0b6d4f397259ec8c8e35662addddd42/37b9b/ai_app_diagram.png 300w,
/static/f0b6d4f397259ec8c8e35662addddd42/ab3c7/ai_app_diagram.png 600w,
/static/f0b6d4f397259ec8c8e35662addddd42/5c837/ai_app_diagram.png 1200w,
/static/f0b6d4f397259ec8c8e35662addddd42/4fe7b/ai_app_diagram.png 1488w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/f0b6d4f397259ec8c8e35662addddd42/5c837/ai_app_diagram.png&quot; alt=&quot;App Flow Sequence Diagram&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;App Flow Sequence Diagram&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;Next, I went for the most simple implementation.
A single screen handled asking for all permissions right at launch. Once those were granted, I’d grab the user’s &lt;em&gt;last
known&lt;/em&gt; location using Android’s &lt;code class=&quot;language-text&quot;&gt;play-services-location&lt;/code&gt; library and feed those coordinates
into &lt;a href=&quot;https://developers.google.com/maps/documentation/geocoding/requests-reverse-geocoding&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Google’s reverse geocoding REST API&lt;/a&gt;
to translate them into an address. At the same time, I
used &lt;a href=&quot;https://developers.google.com/maps/documentation/places/web-service/search-nearby&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Google’s NearbyPlaces REST API&lt;/a&gt;
to get the landmarks close to those coordinates.&lt;/p&gt;
&lt;p&gt;Tapping a button would send a camera intent. From this, I got a low-resolution snapshot, converted it into
a &lt;code class=&quot;language-text&quot;&gt;base64&lt;/code&gt; string format, and sent all that data to the &lt;code class=&quot;language-text&quot;&gt;LLM&lt;/code&gt;. Not pretty, but it got the job done!
&lt;img src=&quot;https://i.imgur.com/cUToXXj.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This was implemented in the quickest and dirtiest way possible. Just make it work. Even the commits were done in a quick
and dirty style.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ git logline

* 35f817e - Implement bot (11/12/2023) &amp;lt;Iury Souza&gt;
* b474f43 - Add support for taking pictures (11/12/2023) &amp;lt;Iury Souza&gt;
* a421a34 - Implement location data fetching (10/12/2023) &amp;lt;Iury Souza&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;Refining&lt;/h1&gt;
&lt;p&gt;Once you’ve got that initial prototype working, refactoring the code and seeing the big picture becomes much easier.&lt;/p&gt;
&lt;p&gt;Those shortcuts you took suddenly scream at you, and you get the space to make important architectural decisions.&lt;/p&gt;
&lt;h2&gt;Delaying decisions&lt;/h2&gt;
&lt;p&gt;So now, on one hand, you have the opportunity to apply all the fancy architectural patterns you’ve learned, the latest
modularization approaches, and even decide which esoteric libraries you’d like to have on the project.
Or, go full wild west – a few messy files, a bunch of singletons… why not?
Now really, finding the balance is key. There’s this spectrum I always think of:&lt;/p&gt;
&lt;p&gt;These choices will shape the entire coding experience from now on, so it’s important to get them right.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 63%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACXElEQVR42qWTy27TQBSGs2gq2lK6aOzY48s4jpPSJnYSO27TStyFaNOUtkICWokF926AAIIHgBdgwUUCsWCDEBvWqE8CkdgBldixQx3m58Qp0IpFBSw+/9LY/ubMnJmUZthfdMP+TGzoBt+GvaGxP+m+66aimQkaswj+ica/Eq9T9AAhu6koKlQ1i6zOoDMLhsl/YVoOpYO8V0SlFiIIAsRTDYRxA/HkjCgHIWiCdRI6JLS/MzMng6mWnKifkG5pRhpuRWYMT47quYSMkZdmviy98pT042OyOt2S48G0NO2ctHh+07RdaLq9nmI0K5UtTV7AubtvsLj2HJMLd+DUlzFoBOhTCkhnx5IcNKvg0TLCuTZaV54iPLiCTEaBZnCReAy+U7h66xVal58gbrbhhCdJ6KMvk0eaZN0ctmrI1ZcQzV3H/KXHiA6dgUJbpJvOLsL5NviWcMgokcgnyhi2a1T5fwiHSOhONFCqHsCYP4MRO4QT/Yuw+bvCNC21X/GQHnWTJf9VhSvtl/ThIxLeSPawW2FaLaBfHSMKvT2MFhHNXkPz4kNqyunkmO0Q6ltCa1uX49Zt6uYSBpjf67La6/JeK0QuPoX6/E0srD1D/fBqUiHbLqRTDyXLNjXmiNaFB+Lo2XsiOn5VuLU5MWKWxR7FEwNqMcl9ZiB4ZVZUjpyn7+6LoLEo1KwmqKhvW8K3Ka84jqBal7VokjKEH1Sxf7wMm7tEPrkdP+n+xJKbZdNNMpNkvXFhWDlaOlVIp/xDoVh6X/KrHcoEm3sdVbM6VH1HZ3x3DP6O8iPx4gdVb34WM5GBIgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/6053c12a40e9b02c868a38d9bd64cd1e/577d7/prototyping_ai_app_complexity_grad.webp 300w,
/static/6053c12a40e9b02c868a38d9bd64cd1e/93ccb/prototyping_ai_app_complexity_grad.webp 600w,
/static/6053c12a40e9b02c868a38d9bd64cd1e/1aa16/prototyping_ai_app_complexity_grad.webp 1200w,
/static/6053c12a40e9b02c868a38d9bd64cd1e/daa23/prototyping_ai_app_complexity_grad.webp 1800w,
/static/6053c12a40e9b02c868a38d9bd64cd1e/ebab6/prototyping_ai_app_complexity_grad.webp 2302w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/6053c12a40e9b02c868a38d9bd64cd1e/37b9b/prototyping_ai_app_complexity_grad.png 300w,
/static/6053c12a40e9b02c868a38d9bd64cd1e/ab3c7/prototyping_ai_app_complexity_grad.png 600w,
/static/6053c12a40e9b02c868a38d9bd64cd1e/5c837/prototyping_ai_app_complexity_grad.png 1200w,
/static/6053c12a40e9b02c868a38d9bd64cd1e/2f4db/prototyping_ai_app_complexity_grad.png 1800w,
/static/6053c12a40e9b02c868a38d9bd64cd1e/949bb/prototyping_ai_app_complexity_grad.png 2302w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/6053c12a40e9b02c868a38d9bd64cd1e/5c837/prototyping_ai_app_complexity_grad.png&quot;
            alt=&quot;prototyping ai app complexity grad&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;sweet spot&lt;/em&gt; depends on your style and the trade-offs you’re willing to make. My rule of thumb is: &lt;strong&gt;Do not rush
these decisions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.martinfowler.com/bliki/Yagni.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;YAGNI&lt;/a&gt; principle is your friend – decide when you have to, when you
fully understand the problem and have viable solutions in mind.
Similarly, you should let your project’s requirements mature before making decisions that are hard to reverse – if you
do, you’ll be sure you’re optimizing for the right goals. And finally, don’t create abstractions just for the sake of
it!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cutting the bullshit&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Still under the same idea, these are some of the things I’m not doing at this stage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Single module. I don’t need a multi-module architecture. There’s absolutely no benefit in bringing a multi-module
setup to the table so early on.&lt;/li&gt;
&lt;li&gt;No custom gradle setup. Getting fancy with gradle build config is also something people waste way too much time. KISS:
Keep it simple stupid&lt;/li&gt;
&lt;li&gt;No code gen. I’m avoiding code gen libs, particularly &lt;a href=&quot;https://kotlinlang.org/docs/kapt.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;KAPT&lt;/a&gt; is out question.&lt;/li&gt;
&lt;li&gt;DI? I went cowboy with a simple Service Locator. No frameworks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously, if I have to change any of these decisions I can, but I don’t need to commit to any of them yet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This ain’t no Wild West either&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’m not gonna go full cowboy either. I’m still following basic coding principles and standards. Separating concerns,
writing tests (where it’s useful at this point), and keeping the code &lt;em&gt;clean&lt;/em&gt; (such a loaded term) and readable, of
course. I’m even using a simple MVI architecture to keep things organized and consistent.&lt;/p&gt;
&lt;p&gt;The MVI thingy looks a bit like this. Consumers can send events, listen to &lt;code class=&quot;language-text&quot;&gt;State&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;Effect&lt;/code&gt; changes. It gets the
job done,
without being overcomplicated. It also forces the implementations to be consistent and this is actually what I’m looking
for here.
I generally shouldn’t have to spend a lot of brain cycles whenever I need to implement something simple. There should be
a standard way of doing common things in this part of the app.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;UserContextViewModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MVIViewModel&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
        UserContextViewEvent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        UserContextViewState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        UserContextViewEffect
        &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setInitialState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UserContextViewEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Besides that I didn’t add the standard clean architecture components like &lt;code class=&quot;language-text&quot;&gt;repositories&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;usecases&lt;/code&gt;. I settle
with simpler abstractions like &lt;code class=&quot;language-text&quot;&gt;LocationProvider&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;GoogleMapsApi&lt;/code&gt;, a simple HTTP Client wrapper that exposed the
Google’s REST APIs.&lt;/p&gt;
&lt;p&gt;I didn’t want to add a lot of layers here because I just didn’t know exactly what I was building. So making things easy
to refactor was important.&lt;/p&gt;
&lt;p&gt;To me, this series of decisions and compromises is what makes the project enjoyable to work with. It’s a side project,
you don’t need to make your life harder than it needs to be. It’s all about finding the right balance between speed and
quality.&lt;/p&gt;
&lt;h3&gt;Tiding things up&lt;/h3&gt;
&lt;p&gt;At this point we’re still only dealing with the happy path, but now we add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A permissions pager with explanations, etc&lt;/li&gt;
&lt;li&gt;A Gradle versions catalog (only Gradle change, promise)&lt;/li&gt;
&lt;li&gt;A Navigation library&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also Refactored the code to use service locators and that basic MVI architecture and finally define theme and colors
properly.&lt;/p&gt;
&lt;p&gt;It still looked ugly, but internally it was much better.&lt;/p&gt;
&lt;h2&gt;Dealing with LLM Prompts&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.imdb.com/title/tt0057012/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Or how I learned to stop worrying and love the &lt;del&gt;bomb&lt;/del&gt; AI&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We’re all used to dealing with HTTP-based APIs, specially RESTful APIs or any kind
of &lt;a href=&quot;https://www.wikiwand.com/en/Remote_procedure_call&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;RPC&lt;/a&gt;. This is the long-standing norm in most software.&lt;/p&gt;
&lt;p&gt;These days we have more sophisticated protocols like &lt;a href=&quot;https://grpc.io/blog/principles/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;gRPC&lt;/a&gt;
or &lt;a href=&quot;https://graphql.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GraphQL&lt;/a&gt;. While they introduce new features and improvements they’re fundamentally under
the same paradigm. In this case, remotely accessing data stored elsewhere.&lt;/p&gt;
&lt;p&gt;LLMs on the other hand unlock a different &lt;em&gt;monster&lt;/em&gt;. We as a client application can now effectively tell this black-box
what we
want it to do, via a &lt;code class=&quot;language-text&quot;&gt;prompt&lt;/code&gt; and it will happily answer us. But not only that. Via some &lt;code class=&quot;language-text&quot;&gt;prompt engineering&lt;/code&gt; techniques
you can alter the way it answers.
Change for example the format of the answer. Maybe you want the answer to contain some metadata about the text, so you
can ask for it to answer in Markdown format. Or maybe you want it to answer in a particular json structure. You can even
ask it for things it doesn’t know, and it will happily hallucinate an answer for you.&lt;/p&gt;
&lt;p&gt;This is a brave new world for anyone, like me, who has never worked with this kind of tech before.&lt;/p&gt;
&lt;h2&gt;Down the Prompt Engineering rabbit hole ⤵️&lt;/h2&gt;
&lt;p&gt;Well, like I said in &lt;a href=&quot;../prototyping-an-ai-app-part-1/&quot;&gt;Part 1&lt;/a&gt;, one could say that I was an early adopter of this tech,
as a normal user at
least. So I knew about the basic prompting text style and was able to come up with this prompt.&lt;/p&gt;
&lt;p&gt;My initial prompt:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;md&quot;&gt;&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;You are a tour guide. You have a laid back personality and like to
enthusiastically
talk about monuments and history. You&apos;re good at
providing facts and bits of trivia of the places people ask you about.
You can also understand city addresses and can pinpoint any location
of the city of &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`input.city`&lt;/span&gt; just with its address.

A tourist just sent you a picture and this address: &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`input.address`&lt;/span&gt;
of a place in &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`input.city`&lt;/span&gt;. The place is near these
locations: &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`input.getLocations()`&lt;/span&gt;

The tourist asks you &quot;what&apos;s in the picture&quot;?
Please answer this person in the style of a tourist guide
who&apos;s enthusiastic about history.

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Immediately start talking about the monument or historic place.
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Don&apos;t start the text with: &quot;Ah, what a splendid
  sight to behold!&quot; or similar phrases.
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Do not repeat the address or the city name in your answer.
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; It&apos;s important that you answer in a way that&apos;s
  informative and interesting.
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Try to figure out which particular part of the monument
  the picture is focusing on and talk about that.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nothing outstanding there, I just provided it with the context and gave it some instructions on how to talk.
For a particular address that I provided it would return this kind of output.&lt;/p&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;txt&quot;&gt;&lt;pre class=&quot;language-txt&quot;&gt;&lt;code class=&quot;language-txt&quot;&gt;Right before your eyes, you&apos;re looking at a powerful photographic
display located near the historic Bornholmer Strasse in Berlin,
which is part of the larger Place of 9 November installation.
This particular image captures an extraordinary moment in history,
an intimate glimpse into the jubilant faces of East Berliners
at the Bornholmer Strasse border crossing on the
night of November 9, 1989 – a night that altered the course of history.

This was the first crossing to open during the fall of the Berlin Wall, marking
a pivotal point in the reunification of Germany. People streamed through here,
in disbelief and elation, as the barriers that once divided a city, and
symbolically the world, were lifted.

In this surrounding area, the picturesque Kirschblutenpfad, or Cherry
Blossom Path, awaits you during the spring, standing in contrast
to the Bosebrucke, another witness to the changes that swept
across this city.

This installation invites contemplation and serves as a portal to the past,
allowing visitors to stand amidst history retold through imagery. As you wander
alongside this precious slice of the city&apos;s timeline, you can almost hear
the echoes of cheers and the rustle of footsteps that marked the beginning
of a new era. it&apos;s not just a photograph, it is a testimony to unity, to change,
and to the enduring spirit of berliners.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Decent response, but I thought it could do better.&lt;/p&gt;
&lt;p&gt;Time to read the manual.&lt;/p&gt;
&lt;h3&gt;Prompt Engineering Techniques&lt;/h3&gt;
&lt;p&gt;At this point I started to look into the different techniques to improve the quality of the responses and I found a few
of
them that I thought were interesting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Role prompting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the one is where you give the AI a role and ask it to play it, which usually increases the quality of responses
quite
a bit. This is a basic one, and I realized that I was already doing it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You are a tour guide.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The role you assign serves as a guideline for the kind of response you’re looking for. It’s good for bringing out
specific styles, tones, or levels of complexity in responses.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;System Roles&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of sending the role prompt via the standard user prompt field, you can set it as
a &lt;a href=&quot;https://platform.openai.com/docs/guides/prompt-engineering/tactic-ask-the-model-to-adopt-a-persona&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;system prompt&lt;/a&gt;.
Which is said
to be injected into every question.&lt;/p&gt;
&lt;p&gt;It can be instructions guiding the LLM Behavior like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Assistant description: profession, persona, etc&lt;/li&gt;
&lt;li&gt;Context: information, preferences, etc&lt;/li&gt;
&lt;li&gt;Response format: email, summary, &lt;code class=&quot;language-text&quot;&gt;json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Boundaries: don’t talk about this, don’t repeat that, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Solo Performance Prompting (SSP)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is a method where a single Large Language Model (LLM) acts like multiple experts (or personas) working together to
solve complex tasks.&lt;/p&gt;
&lt;p&gt;This is important because it improves the model’s ability to handle tasks that require deep knowledge and complex
reasoning, making it more effective and versatile.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A basic example of its use could be an LLM playing the roles of a historian, a scientist, and a travel guide to answer
a question about the historical and scientific significance of the Eiffel Tower.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The model, using SPP, would combine insights from these different personas to provide a comprehensive and well-rounded
answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Few shot prompting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is a technique where you provide a few examples that demonstrate the kind of output you’re looking for before
sending the actual query.&lt;/p&gt;
&lt;p&gt;These few examples (or “shots”) give the AI with a pattern to follow when dealing with the actual task.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zero shot prompting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By appending words like &lt;em&gt;“Let’s think step by step.”&lt;/em&gt; to the end of a question, LLMs are able to generate a chain
of thought that answers the question. From this chain of thought, they are able to extract more accurate answers.&lt;/p&gt;
&lt;h2&gt;What worked for me&lt;/h2&gt;
&lt;p&gt;You see, this still feels more like invoking incantations than doing actual software development.
Besides, the qualitative measurement of these changes are super subjective and hard to assert. Most of the times you’re
left with going
with your feeling.&lt;/p&gt;
&lt;p&gt;But some simple tricks can make working with this way more manageable. For example, asking for an output format is super
useful to interface with the unstructured nature of the LLMs outputs and the structured nature of our code base.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Role Prompting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the most basic one and think we’re all pretty much doing it at this point. Tell the AI its job, its role&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;md&quot;&gt;&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;You are a tour guide. You have a laid back personality and like to
enthusiastically talk about monuments and history. You&apos;ve been
living in &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`input.city`&lt;/span&gt; for the past 30 years and know the city like
the back of your hand. Your main target audience are tourists aged from
18 to 45, so you have a casual and friendly speech tone.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Output formatting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the best example of managing this complexity.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;md&quot;&gt;&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;Your output format is a json file without the code block
(&lt;span class=&quot;token code-snippet code keyword&quot;&gt;`json content`&lt;/span&gt;)

{  
 &quot;title&quot;: &quot;string&quot;,  
 &quot;content&quot;: &quot;string&quot;  
}

The &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`title`&lt;/span&gt; should describe the monument
or historic place. Keep it short, 5 words max.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This was great. Now with the title I could actually create a screen instead of justing rendering a text.&lt;/p&gt;
&lt;p&gt;There was still a problem though. Whenever I would send a picture of something that’s not a monument, it would start
hallucinating (technical term for talking gibberish) and going into tangents just to keep talking. So I added this to
the prompt:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;md&quot;&gt;&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;Your output format is a json file without the code block
(&lt;span class=&quot;token code-snippet code keyword&quot;&gt;`json content`&lt;/span&gt;)

{  
 &quot;isValidRequest&quot;: &quot;boolean&quot;  
 &quot;title&quot;: &quot;string&quot;,  
 &quot;content&quot;: &quot;string&quot;  
}

The &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`isValidRequest`&lt;/span&gt; field should be &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`false`&lt;/span&gt; if the image is &lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;NOT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt;
related to a monument or historic place and &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`true`&lt;/span&gt; otherwise.  
When &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`isValidRequest`&lt;/span&gt; is &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`false`&lt;/span&gt;, the &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`title`&lt;/span&gt; and &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`content`&lt;/span&gt; should be omitted.
The &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`title`&lt;/span&gt; should describe the monument or historic place.
Keep it short, 5 words max.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, I had &lt;strong&gt;error handling built-in the output&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Few shot prompting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This idea worked pretty well. What I did was basically pick one of the outputs and change the parts which I didn’t like.
Replaced some words, changed the tone of the writing and etc. This caused the other answers to follow a general
underlying pattern that it picked from that writing. Which I feel is a great idea.&lt;/p&gt;
&lt;p&gt;Sometimes it’s hard to explain exactly what you want, so an example fills in the gaps for you.&lt;/p&gt;
&lt;h2&gt;Out of the prompt rabbit hole ⤴️&lt;/h2&gt;
&lt;p&gt;You can spend quite a while improving your prompts, there are many other techniques that you can and should try. My
final prompt had quite few more things into it, but I feel there’s a point where adding new things stop being effective,
so it’s a balancing act too. And like I said in the beginning all this few more like throwing a bunch of shots into a
wall and seeing what sticks. So keep that in mind.&lt;/p&gt;
&lt;h2&gt;Back to refining - Making it pretty-er&lt;/h2&gt;
&lt;p&gt;Prototypes are nice and all but a nice app is much better. But what kind of UI should we have here?
This is sort of an unusual app so I couldn’t &lt;del&gt;just copy&lt;/del&gt; take inspiration from another bigger app.&lt;/p&gt;
&lt;p&gt;I as a user just wanted to go from camera to audio guide as fast as possible. The main problem was big loading times and
how to display the audio guide in an interesting way.
This app UI should be minimal anyway so this shouldn’t be rocket science, not for a side-project.&lt;/p&gt;
&lt;p&gt;I had some notes about what I wanted to build:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;md&quot;&gt;&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Home: catchphrase: (CallToAction)
  &lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Start camera
  &lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Take picture
    &lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Show fancy animation for uploading picture
    &lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Request audio content and stylized img based on the picture uploaded
    &lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; Show blurred out stylized pic as background for text streaming.
    &lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; play audio on top of blurred background&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So I had 3 main UI problems to solve.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Home&lt;/li&gt;
&lt;li&gt;Loading&lt;/li&gt;
&lt;li&gt;Playback&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Massive loading times&lt;/strong&gt;
ong loading times are a real pain point – my app was taking an average of 35 seconds to generate text and audio!
That’s a lot of waiting. The plan was to eventually implement text streaming chatGPT which improves the perception of
waiting a lot. Implementing that was part of the plan, but not at this stage.&lt;/p&gt;
&lt;p&gt;I wanted to get the basics down first, and since I would still need to wait for the audio to be generated anyway, so I
would still need a decent loading screen.&lt;/p&gt;
&lt;p&gt;So I went with a combination of lottie animations and displaying randomly selected sentences in the style of Maxis (from
The Sim &amp;#x26; Sim City series) loading screens (try to spot them on the app demo video).
This was fun bit that I added, it may start to feel repetitive after a while, at this stage it got the job done (making
waiting easier).&lt;/p&gt;
&lt;figure style=&quot;text-align:center;&quot;&gt;
  &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;&gt;
      &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 74.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAAPABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABwAGCf/EABcBAAMBAAAAAAAAAAAAAAAAAAEFBgf/2gAMAwEAAhADEAAAAeigYY4ovSSK60z7/8QAGxAAAwACAwAAAAAAAAAAAAAAAwQFAQYREhP/2gAIAQEAAQUCsUe4qkSswJsvJm7pSGd260FVilj1/8QAIhEAAQMDAwUAAAAAAAAAAAAAAgEDBBESEyExUQAFBiIy/9oACAEDAQE/Ae1v+MMQmhlBEekuMIkkpkV6QoE5kR5GDbEEE0EmsJLXEQraVUyuOCl52p63lbv8103125156//EACMRAAIBAgQHAAAAAAAAAAAAAAECAxESAAQhMQUiIzJCUXH/2gAIAQIBAT8BzkXGpMy5y7ypEJOkIZUjDKtlhcMTVSQ148w2oobFQ8iXd1q1+0FdtN/WmP/EACQQAAMBAAEEAQQDAAAAAAAAAAECAwQREhMhIgUAFCMxFTJB/9oACAEBAAY/AtSZQt9ksltEcgZUfQ0+ksgenCKIQNNt1Hc0Pkz2+2i9PKUqM6xzPrXNH5F6zpj5bc2FZdIVK111rJ1zxQfkNsoAaj9styrzP+zopSiEEgrRG9kcfp0b2RuVbyD9NXL+HRTOYPplpbLraDK6Uz9w/G7DJXRynfzVhoEnrIUVaMCc9d24ZBNJ9uXyvR0LPQuhHjT+EponsFERhu7jaeQx6x18A9H9fPHtSn7YnzSoSlW8+9XUNR+XI9vr/8QAGBABAQEBAQAAAAAAAAAAAAAAAREAIUH/2gAIAQEAAT8hM5W1epBSWAdh9C81MDmhmJqBXcnaYynGEAQTT2AziToRnimHJYx0u6j2HIjjdtdUIZo+ArFG/9oADAMBAAIAAwAAABDE3//EABoRAQACAwEAAAAAAAAAAAAAAAEhQQARMXH/2gAIAQMBAT8QMiAA7Ui8cvC+PgqVEsUrKLUWe//EABoRAQADAAMAAAAAAAAAAAAAAAEAESExQVH/2gAIAQIBAT8Q1gefFpmvNMlNFtHqgPcAA6uhyrCf/8QAFxABAQEBAAAAAAAAAAAAAAAAAREAIf/aAAgBAQABPxAsFIZ/aAXyzw0msYajXOXQ5pKcxX4g8AKCXeU9n6Htkieu+S+HFJyUiy3OiALVo6jqYLx//9k=&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/4d9f932a59c23ae8144f2b4305b22119/577d7/the-sims.webp 300w,
/static/4d9f932a59c23ae8144f2b4305b22119/93ccb/the-sims.webp 600w,
/static/4d9f932a59c23ae8144f2b4305b22119/1aa16/the-sims.webp 1200w,
/static/4d9f932a59c23ae8144f2b4305b22119/85558/the-sims.webp 1228w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/4d9f932a59c23ae8144f2b4305b22119/622f1/the-sims.jpg 300w,
/static/4d9f932a59c23ae8144f2b4305b22119/58041/the-sims.jpg 600w,
/static/4d9f932a59c23ae8144f2b4305b22119/0516a/the-sims.jpg 1200w,
/static/4d9f932a59c23ae8144f2b4305b22119/5d1f1/the-sims.jpg 1228w&quot; sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot; type=&quot;image/jpeg&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/4d9f932a59c23ae8144f2b4305b22119/0516a/the-sims.jpg&quot; alt=&quot;Random technical-sounding loading sentences&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Random technical-sounding loading sentences&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Karaoke Playback&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The second thing I wanted to solve was the Playback screen. I decided to implement a feature similar to spotify’s synced
lyrics. Here, I had the audio and the text that would be played, but I didn’t have the actual speech rhythm and you can’
t expect every word to be said following the same speech rhythm pattern, otherwise it would sound awfully like the old
windows TTS from 20 years ago. These days we have more natural sounding voices, which bring this problem back.&lt;/p&gt;
&lt;p&gt;My solution was to get the current audio playback percentage and sync that to the &lt;code class=&quot;language-text&quot;&gt;scrollState&lt;/code&gt; of the component that
would display the text.
This component would have fading text borders in a way that the current sentence being played would be displayed in the
center and the rest of the text would be faded out. This kinda creates the illusion of a karaoke style component, even
when it’s not perfect.
Good enough for me.&lt;/p&gt;
&lt;p&gt;The next piece of the puzzle was making the text more easier to parse for us.
For that, I just modified the prompt to get it format the text with markdown, so I could render it in a more interesting way. I added this to
the prompt&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;The text is in the content field should use bold markdown formatting for the
following cases:
- Dates.
- Names of places.
- Addresses.
- Names of people.
- Historic events.
- Name of monuments.
- Names of historic figures.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To do that I used the nice &lt;a href=&quot;com.halilibo.richtext.markdown&quot;&gt;compose-rich-text&lt;/a&gt; library which literally takes of
everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dev Mode&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Geolocation coordinates are crucial in this app. To fine-tune the output, you’ll typically provide a picture and
a lat/lng. Real-world testing gets repetitive fast – you don’t want to take a new picture for every test.&lt;/p&gt;
&lt;p&gt;That’s why I created a Dev Mode screen. Here, you can enter a picture URL and paste some coordinates, skipping the usual
photo capture and location tracking. Later I even expanded this system to stub out the ChatGPT API for better testing
the karaoke flow.&lt;/p&gt;
&lt;p&gt;This setup was a lifesaver during UI development. With quick tweaks and no cumbersome photo-taking, I could iterate on
the design much faster.&lt;/p&gt;
&lt;h3&gt;Ironing it out&lt;/h3&gt;
&lt;p&gt;Now I only wanted to do a couple of improvements like finally adding text streaming support and supporting Google’s
Gemini SDK.&lt;/p&gt;
&lt;p&gt;Text streaming was pretty easy to support, since it was built into both SDKs and my implementation was flexible enough
to support it. So while text was streaming I displayed it like a loading page, and only showed the final audio guide
karaoke after the
TTS was loaded.&lt;/p&gt;
&lt;p&gt;Finally, Gemini was released just a week prior to this stage, so I wanted to check that out. Living in the EU I knew it was
blocked, but that didn’t stop me from trying. So I just added a dev-mode option to enable it and it worked as
expected - a bit underwhelming at this point, but I wanted to
have the freedom to switch engines if I wanted to.&lt;/p&gt;
&lt;h1&gt;App Pitch&lt;/h1&gt;
&lt;p&gt;Here’s a quick pitch of the app. It’s not the final version, I posted this before it had text streaming support.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I&amp;#39;ve been toying with the chat-GPT vision API since last week and I&amp;#39;ve put together this prototype.&lt;br&gt;&lt;br&gt;I&amp;#39;m still exploring what it&amp;#39;s capable of, but so far I&amp;#39;m impressed. &lt;br&gt;&lt;br&gt;Apart from improving the rubbish UX/UI, the next step would be to use word embeddings to improve its accuracy &lt;a href=&quot;https://t.co/cABPZ2mY5U&quot;&gt;pic.twitter.com/cABPZ2mY5U&lt;/a&gt;&lt;/p&gt;&amp;mdash; iury souza (@IurySza) &lt;a href=&quot;https://twitter.com/IurySza/status/1738225405000310794?ref_src=twsrc%5Etfw&quot;&gt;December 22, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;h1&gt;Final touches&lt;/h1&gt;
&lt;p&gt;This is the final version of the app I shared just before taking my yearly 20 days off to travel to Brazil.&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I finished ironing out some things here like rendering partially loaded content while streaming text and improving the &amp;quot;karaoke&amp;quot; audio guide screen. I try to keep the highlighted text in sync with the audio and added Netflix inspired skipping button animations 😛.&lt;br&gt;Also Gemini (+) &lt;a href=&quot;https://t.co/yQLzF6FJBP&quot;&gt;pic.twitter.com/yQLzF6FJBP&lt;/a&gt;&lt;/p&gt;&amp;mdash; iury souza (@IurySza) &lt;a href=&quot;https://twitter.com/IurySza/status/1744812201989304585?ref_src=twsrc%5Etfw&quot;&gt;January 9, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;h1&gt;Next steps&lt;/h1&gt;
&lt;p&gt;Okay, I’m excited to get this app published! Before a full release, I’ll definitely take a better look into the pricing
model and try to get it published for beta-testers only the coming weeks.&lt;/p&gt;
&lt;p&gt;This is far from a finished product and I know what you might be thinking:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It doesn’t get things right all the time. It hallucinates or it is just stealing content from other people!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And I hear you. I also think it’s not perfect. That’s why I’m not monetising it.&lt;/p&gt;
&lt;p&gt;Although there are a few things I could do to improve the quality of the answers by a LOT.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;RAG (Retrieval Augmented Generation):&lt;/strong&gt;
In sum, this lets the LLM query a vector database for content related to the question and inject that content into
its context, which enables it to pretty much solve the hallucination problem in a lot of cases.
You can read more about that here.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Google Gemini 1.5:&lt;/strong&gt;
This recent update massively increases the context window. More context means better answers and could help with many
of the problems RAG addresses. I’m curious to see where this leads!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Curious about the App?&lt;/h1&gt;
&lt;p&gt;The app is still not ready for a full release, but I’d be happy to get some feedback from you. If you’re interested in
trying it out, send me an &lt;a href=&quot;mailto:iurysza@gmail.com?subject=App%20Beta%20Feedback&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;email&lt;/a&gt; and I’ll include you in the beta.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This was such a fun project! I can’t remember the last time a new technology blew me away like this. When ChatGPT went
mainstream in 2022, it felt like magic. Even now, I’m still amazed by some of the things it can do.&lt;/p&gt;
&lt;p&gt;Of course, there are plenty of issues, and it raises serious questions about how we’ll adapt as a society, but part of
me can’t help but feel excited about the prospects of integrating this into our apps and tools.&lt;/p&gt;
&lt;p&gt;Something I felt about this project from the start was that this will get built by someone. I’m sure that in a few
months, a big company will pick this up. Even Zuck talked about how he wants Meta’s Ray-Ban Glasses to be able to answer
historical data of a building, so I’m pretty sure that will cover this audio guide use case too. Anyway, it is nice to
play around with the idea while there’s still nothing out there.&lt;/p&gt;
&lt;p&gt;I learned a ton about LLMs and how to turn them into real products. Plus, it was fun to build some weird UI components!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I eventually built the dedicated backend for this prototype to explore RAG, retrieval, and audio generation properly. I wrote a follow-up on the deployment side here: &lt;a href=&quot;../from-github-actions-to-cloud-build/&quot;&gt;From GitHub Actions to Cloud Build&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Planning and documenting a side project]]></title><description><![CDATA[This is the second part of a series of articles about prototyping an AI audio guide app.
Missed the other parts? There you go: Part 1 and…]]></description><link>https://iurysouza.dev/prototyping-an-ai-app-part-2/</link><guid isPermaLink="false">https://iurysouza.dev/prototyping-an-ai-app-part-2/</guid><pubDate>Sat, 13 Jan 2024 22:39:07 GMT</pubDate><content:encoded>&lt;p&gt;This is the second part of a series of articles about prototyping an AI audio guide app.
Missed the other parts? There you go: &lt;a href=&quot;../prototyping-an-ai-app-part-1/&quot;&gt;Part 1&lt;/a&gt; and &lt;a href=&quot;../prototyping-an-ai-app-part-3/&quot;&gt;Part 3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the first part we talked about how to go from an idea to a basic prototype. Now, in this short article I’d like to
give my 2 cents on how to document and plan the execution of that idea, or &lt;em&gt;any&lt;/em&gt; side project.&lt;/p&gt;
&lt;p&gt;Mind you that this is not a tutorial, but rather a case study of how I approached this topic. Besides, it’s important to
say that this works best for very early stage projects, where you have a lot of questions and not many answers.&lt;/p&gt;
&lt;h1&gt;What’s next?&lt;/h1&gt;
&lt;p&gt;I was sure that the core functionality worked and I could start building an app around that idea.
But at this point of the project there are a lot of open questions, like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How would I structure the project and what architecture to use.&lt;/li&gt;
&lt;li&gt;Which libraries would be needed?&lt;/li&gt;
&lt;li&gt;How would the app flow would look like? What about the design? The UX?&lt;/li&gt;
&lt;li&gt;What permissions do I need? When to ask them?&lt;/li&gt;
&lt;li&gt;How could I improve the prompts? Is prompt engineering an actual thing?&lt;/li&gt;
&lt;li&gt;How would I display the actual output to the user? Show a text? Just play the audio? How does the UI for an AI audio
guide look like?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The more you think about the more questions you get.&lt;/p&gt;
&lt;h1&gt;Obsidian and Mind Maps&lt;/h1&gt;
&lt;p&gt;The first thing you need to do at this point is putting all those questions in a place where you can see them all at
once.
Then you start answering and organizing the answers into categories. This is where mind maps come in handy.&lt;/p&gt;
&lt;p&gt;Notes help a lot, but you’ve probably been at this place where you write down a bunch of notes, but you can’t make sense
of them. Or they don’t seem to connect, and you can’t see the big picture. To fix that we can use something
like &lt;a href=&quot;https://obsidian.md/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Obsidian&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is already my go-to note-taking tool, but it’s also great for mind mapping with the &lt;code class=&quot;language-text&quot;&gt;canvas&lt;/code&gt; feature. Each note is a
node, and you can link
them.
Notes can even reference each other with hyperlinks, so you can easily navigate between them.
It’s all going there - diagrams, designs, ideas, case studies, laid out in a big mind
map with all those markdown files. So, let’s get to it.&lt;/p&gt;
&lt;p&gt;The two starting nodes were these:
&lt;img src=&quot;https://i.imgur.com/gNDQISW.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;From there you can start to break down the project into smaller parts and try to focus on each one at a time.&lt;/p&gt;
&lt;h2&gt;Research and documentation&lt;/h2&gt;
&lt;p&gt;Those starting nodes would prompt questions I had to answer, which lead to other topics I had to research and document.
For example, I would research which APIs I could use, then document the steps to use each of them, think API request
examples, dashboard panels, links, api keys, and so on.
Then, when I was ready to integrate it to the project I had a clear path to follow.&lt;/p&gt;
&lt;p&gt;For example, to answer the question “how to add location context to the LLM prompt” I had a simple idea:
Use the device’s GPS to get exact coordinates, then use the Geocoding API to get
the precise address, finally we could fetch the top 5 tourist attractions near those coordinates.&lt;/p&gt;
&lt;p&gt;After some research I figured that the Places SDK could solve that, but after testing I realised it doesn’t filter for
tourist attractions, so I had to resort to using the plain REST API.&lt;/p&gt;
&lt;img src=&quot;https://i.imgur.com/sb10ey8.png&quot; classname=&quot;hoverable&quot;&gt;
&lt;p&gt;Having done that, I could step back and take a look at the big picture. I could see the whole process of getting the
data I needed to build the experience I wanted.&lt;/p&gt;
&lt;p&gt;This process also worked when defining how I wanted the app flow to look like. Starting from sequence Diagrams,
wireframe
sketches, and finally a higher-fidelity design.&lt;/p&gt;
&lt;p&gt;All this information linked in the same space, &lt;strong&gt;makes it easy to navigate and understand the project as a whole&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here is the full mind map. I included information about decisions and documentation on prompt engineering, design, UX,
sequence flows, even a &lt;strong&gt;Kanban board&lt;/strong&gt; for tracking the feature backlog.
Having worked on other side projects where I didn’t document anything, I can say that this approach is a game changer.&lt;/p&gt;
&lt;video width=&quot;700&quot; controls&gt;
  &lt;source src=&quot;/a6b6356fb3b04141ee56ae71ce8bae9b/mind-map-demo.webm&quot; type=&quot;video/mp4&quot;&gt;
&lt;/video&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;This whole documentation topic might seem like a chore or just irrelevant, but especially when it comes to side
projects, they can be crucial for taking an idea to fruition. But we usually just don’t pay enough
attention to it, which often leads to wasted effort and sometimes giving up on an idea altogether (been there, done
that). Besides this, there are the obvious benefits of having a clear plan and an organized place to find
important information.&lt;/p&gt;
&lt;p&gt;All of this is vital for keeping side projects alive. It’s easy to lose interest and move on when you hit roadblocks, so
having a dedicated space to store your thought process makes it much easier to pick up where you left off.&lt;/p&gt;
&lt;p&gt;In the next and final article, we’ll talk about a mental model for architecture decisions, trade-offs, learnings, and
guidelines. See you there!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;../prototyping-an-ai-app-part-3/&quot;&gt;Jump to Part 3&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[From idea to code]]></title><description><![CDATA[This is the first part of a series of articles about prototyping an AI audio guide app.
Looking for the other ones? Part 2 Part 3 Happy new…]]></description><link>https://iurysouza.dev/prototyping-an-ai-app-part-1/</link><guid isPermaLink="false">https://iurysouza.dev/prototyping-an-ai-app-part-1/</guid><pubDate>Tue, 09 Jan 2024 22:39:07 GMT</pubDate><content:encoded>&lt;p&gt;This is the first part of a series of articles about prototyping an AI audio guide app.
Looking for the other ones?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;../prototyping-an-ai-app-part-2/&quot;&gt;Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;../prototyping-an-ai-app-part-3/&quot;&gt;Part 3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy new year! A bit late, but here we are. This is the first part of a series of articles where I’ll be writing about
my first experience building an AI-powered mobile app. This was a fun learning experience and I hope to share something
useful here.&lt;/p&gt;
&lt;p&gt;Now, first, a bit of a background story. You can safely skip this part if you want.&lt;/p&gt;
&lt;h1&gt;Background story&lt;/h1&gt;
&lt;p&gt;I’m the kind of person who’s naturally curious, I can nerd out about literally anything and that’s probably why I’ve
picked
up so many hobbies over the years. It happens that I got into tech, so I’ve been nerding about that the
longest. Always keeping an eye on the ever-changing trends in the industry.&lt;/p&gt;
&lt;p&gt;My curiosity naturally drew me to AI around 2017, although from a distance. I remember talking about it with a
flatmate (and great friend) of mine every evening. We’d watch videos on the new developments and the new papers coming
out. I even convinced him to join me in &lt;a href=&quot;https://confirm.udacity.com/YPPVJHMY&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Udacity’s AI Foundations Nanodegree&lt;/a&gt;,
which we completed in 2018, basic stuff obviously, it was clear to me that this field would boom pretty soon.&lt;/p&gt;
&lt;p&gt;Now, you can imagine how amazed I was
when &lt;a href=&quot;https://www.theverge.com/2019/2/14/18224704/ai-machine-learning-language-models-read-write-openai-gpt2&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ChatGPT 2&lt;/a&gt;
came out, let alone ChatGPT 3. In 2023 AI went mainstream like we
have never seen before.
The hype was on par with – maybe even crazier than – all that crypto buzz, but this time actually useful products were
being built.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Chat-GPT Hype-train&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I signed up as soon as it launched. For a while I obsessed with prompt engineering hacks and trying to
see what this thing could do and how I could squeeze this new tech into my workflow. Writing, translation,
coding, learning new things, you name it.&lt;/p&gt;
&lt;p&gt;The next AI milestone for me was the &lt;a href=&quot;https://www.raycast.com/pro&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Raycast Pro&lt;/a&gt; which added a chat-gpt wrapper
interface. Now I could definitely incorporate it to my day to day tasks. Having tried lots of chrome extensions,
Obsidian plugins and what not, this one felt natural. Now I was really using AI a lot for mundane tasks.&lt;/p&gt;
&lt;p&gt;In my experience I think you really need to try and use this tech for you to really see what it’s capable off. A lot of
smart people around me were writing it off because they had and incompatible mental model of it.&lt;/p&gt;
&lt;p&gt;Sure, I hadn’t yet had that mind-blowing lightbulb moment, especially for anything I could build as a mobile engineer.
Most apps I saw were about chatbots or image generating, which didn’t really click with me.&lt;/p&gt;
&lt;p&gt;Then, comes Q4 and
the &lt;a href=&quot;https://openai.com/blog/chatgpt-can-now-see-hear-and-speak&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;multimodal models start getting released&lt;/a&gt;. So, now
ChatGPT can understand pictures? Ok. This is interesting!
I took a look at the OpenAI API docs – that vision API seemed crazy simple. So one chilly December night, I figured, why
not give it a shot?&lt;/p&gt;
&lt;h1&gt;Picking the right tools&lt;/h1&gt;
&lt;p&gt;So, the first decision is which language to use, right? OpenAI has official SDKs for &lt;code class=&quot;language-text&quot;&gt;Python&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;JavaScript&lt;/code&gt;. I could
work with both, but they always feel a bit like speaking a second language – I get by, but there are too many little
mistakes and one thing I’ve learned over the years is that learning multiple things on a new project is going to slow
you down.
So, before picking one of those I researched &lt;code class=&quot;language-text&quot;&gt;Kotlin&lt;/code&gt; SDKs and found the
&lt;a href=&quot;https://github.com/aallam/openai-kotlin&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;openai-kotlin&lt;/a&gt; library: A kotlin multiplatform alternative that offers
almost every feature of the official sdks, so I went with it.&lt;/p&gt;
&lt;p&gt;✅ Check!&lt;/p&gt;
&lt;h1&gt;Inspiration&lt;/h1&gt;
&lt;p&gt;Ok, now what to build?&lt;/p&gt;
&lt;p&gt;Well, I had just spent a week in Andalusia, South of
Spain and had the fresh memory of walking around the city and juggling between multiple apps to figure out the monuments
and what not.&lt;/p&gt;
&lt;p&gt;Imagine this: You’re walking around Granada and want to know more about you’re looking at. You’re visiting,
the &lt;a href=&quot;https://en.wikipedia.org/wiki/Alhambra&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Alhambra&lt;/a&gt;? Great, they’ve got an
audio guide app web app. Next day, you’re at a medieval church in the city. Great, another app. Then you stumble upon
this amazing
monument – no app, so you’re stuck Googling (or asking ChatGPT). And sometimes you just see something incredible but
have
zero clue what it is – cue frantically searching Google Maps to even find the name!&lt;/p&gt;
&lt;p&gt;Okay, sure, this is what happens when you don’t plan ahead. Guilty as charged. But still, there’s a problem to solve
here.&lt;/p&gt;
&lt;p&gt;I knew what to build.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What if there was an app where you snap a photo and ✨! – instant audio guide?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Honestly, even if I was the only one who used it, that sounded awesome.
Alright then, time to roll up the sleeves!&lt;/p&gt;
&lt;h1&gt;1st Prototype: Just use ChatGPT&lt;/h1&gt;
&lt;p&gt;Now, the beauty of this Generative AI thing is that it’s super easy to prototype. This is what I did:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Went to open AI platform page. &lt;em&gt;(This is important because, the ChatGPT ui that most people are used to is a product
built on top of their APIs, so you need to use this platform to simulate the answers you’d get from their apis)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Uploaded a picture of the Berlin Brandenburg Gate,&lt;/li&gt;
&lt;li&gt;Added a few instructions to the prompt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And there you have it!
Not perfect, but a surprisingly solid answer. I tested a few other landmarks and it kept working well enough.&lt;/p&gt;
&lt;p&gt;I was sold on this idea and these tests gave me confidence to start building the app. But not a mobile app, not just
yet.&lt;/p&gt;
&lt;h1&gt;2nd Prototype: A Kotlin App&lt;/h1&gt;
&lt;p&gt;Instead of jumping head first into building the android app, I wanted to first iterate on the core implementation using
the simplest approach.&lt;/p&gt;
&lt;p&gt;This should be really straight forward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Call the API passing an image file and a prompt.&lt;/li&gt;
&lt;li&gt;Explore the ergonomics. E.g.:
&lt;ul&gt;
&lt;li&gt;File uploading via the api&lt;/li&gt;
&lt;li&gt;How does text streaming works?&lt;/li&gt;
&lt;li&gt;How long does it take to answer&lt;/li&gt;
&lt;li&gt;How to integrate with the TTS (Text-To-Speech) API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You see, spinning up an emulator, or the basic infrastructure to test this basic flow of on a native android app is not
as straight forward as calling a &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; function and that’s what I wanted.&lt;/p&gt;
&lt;p&gt;And this is literally what the code looked like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; TTS &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        text&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Path&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; runCatching &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; openAI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; speechRequest &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            model &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ModelId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tts-1&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text
            voice &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Voice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Shimmer
            &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;speed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.15&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; audio &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; openAI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;speech&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;writeToFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;audio&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; AudioGuide &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        input&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TourGuideInput&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;String&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; runCatching &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; openAI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; chatCompletionRequest &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            model &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ModelId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gpt-4-vision-preview&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            messages &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                user &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    content &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;buildPrompt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;imageUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            maxTokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;600&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
         openAI&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chatCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;choices
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;orEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        city &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Berlin&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        imageUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://static.dw.com/image/52796179_605.jpg&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    AudioGuide&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onFailure&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;printStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onSuccess&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; response&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token string-literal multiline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&quot;
                        | Parameter | Value |
                        |---|---|
                        | City | &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;city&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; |
                        | ImageUrl | &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;imageUrl&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; |
                        | Output | &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;response&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; |
                &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trimIndent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            TTS&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that was it. I built this on that very evening and was impressed with the results.&lt;/p&gt;
&lt;h1&gt;Improving the prototype&lt;/h1&gt;
&lt;p&gt;So that prototype worked, but as soon as you start using pictures that are not super clear, or basically whenever you
ask about not so famous sights, then the magic starts to break off.&lt;/p&gt;
&lt;p&gt;Which makes sense. This thing is not magic, it has lots of flaws, but if you give it enough context things start to
improve. On the example above you can see that I already was telling it about the city where it was.
This helped clear out a lot of confusion because it reduces the &lt;em&gt;search space&lt;/em&gt; from anywhere in the world to just one
city. But, can we do better?&lt;/p&gt;
&lt;p&gt;Well, the idea was to eventually build a mobile app and if we’re an app, then we can get the user’s geolocation
coordinates.
We can also do &lt;code class=&quot;language-text&quot;&gt;Reverse geocoding&lt;/code&gt; (address lookup)! Telling it the city and street name would be nice… but upon
testing this (by manually putting the address into the prompts) I saw that it still didn’t solve the problem.&lt;/p&gt;
&lt;p&gt;Next idea: Google Places SDK! I could use that to find the tourist attractions nearby.
It’s a plan! Now we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Send the AI a picture&lt;/li&gt;
&lt;li&gt;create a nice prompt telling it:
&lt;ul&gt;
&lt;li&gt;The address where it was taken&lt;/li&gt;
&lt;li&gt;The top attractions nearby&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, I decided that I was going to focus on a single city for this prototype. Since I was dependent on google places
api, I couldn’t trust the quality of its answers for just any city (same for chat-gpt). So, I decided to focus on
Berlin, the city I live in.&lt;/p&gt;
&lt;h1&gt;Validating the Solution&lt;/h1&gt;
&lt;p&gt;To validate all these assumptions, I still didn’t want to invest the time into implementing it, so I hardcoded the data.&lt;/p&gt;
&lt;p&gt;I researched a bunch of &lt;em&gt;hidden&lt;/em&gt; landmarks examples and used their data to feed into the LLM and then recorded the
answers. Then, looking at the output, I was again happy with it.&lt;/p&gt;
&lt;p&gt;This is an example of the output I got:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;City&lt;/th&gt;
&lt;th&gt;Berlin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Address&lt;/td&gt;
&lt;td&gt;Bornholmer Str. 61, 10439 Berlin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TopLocations&lt;/td&gt;
&lt;td&gt;Place of 9 November, Bosebrucke, Kirschblutenpfad, Scheifengarten&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ImageUrl&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://berlintraveltips.com/wp-content/uploads/2021/07/Platz-des-9-November-Berlin-free-activities.jpg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://berlintraveltips.com/wp-content/uploads/2021/07/Platz-des-9-November-Berlin-free-activities.jpg&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;Right before your eyes, you’re looking at a powerful photographic display located near the historic Bornholmer Strasse in Berlin, which is part of the larger Place of 9 November installation. &lt;br/&gt;This particular image captures an extraordinary moment in history, an intimate glimpse into the jubilant faces of East Berliners at the Bornholmer Strasse border crossing on the night of November 9, 1989 – a night that altered the course of history. &lt;br/&gt;This was the first crossing to open during the fall of the Berlin Wall, marking a pivotal point in the reunification of Germany. &lt;br/&gt;People streamed through here, in disbelief and elation, as the barriers that once divided a city, and symbolically the world, were lifted. &lt;br/&gt;In this surrounding area, the picturesque Kirschblutenpfad, or Cherry Blossom Path, awaits you during the spring, standing in contrast to the Bosebrucke, another witness to the changes that swept across this city. &lt;br/&gt;This installation invites contemplation and serves as a portal to the past, allowing visitors to stand amidst history retold through imagery. &lt;br/&gt;As you wander alongside this precious slice of the city’s timeline, you can almost hear the echoes of cheers and the rustle of footsteps that marked the beginning of a new era. It’s not just a photograph, it is a testimony to unity, to change, and to the enduring spirit of Berliners.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now I felt confident that this would at least make a good prototype.&lt;/p&gt;
&lt;h1&gt;Wrap up&lt;/h1&gt;
&lt;p&gt;Starting small, we can use generative AI like ChatGPT to play around, refine ideas, and turn them into something
interesting. The key is - prototype and iterate!&lt;/p&gt;
&lt;p&gt;By now, the mobile app is finished. But these series of articles are intended to document this process and hopefully
give you insights along the way.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;../prototyping-an-ai-app-part-2/&quot;&gt;Jump to Part 2&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Grounding Beliefs]]></title><description><![CDATA[If you came for the docs, head over to the repo. Grounding Beliefs I saw a tweet last year that stuck with me: That’s exactly what I’d been…]]></description><link>https://iurysouza.dev/automating-project-architecture-graphs/</link><guid isPermaLink="false">https://iurysouza.dev/automating-project-architecture-graphs/</guid><pubDate>Thu, 16 Mar 2023 11:46:37 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;If you came for the docs, head over to &lt;a href=&quot;https://github.com/iurysza/module-graph&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;the repo&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Grounding Beliefs&lt;/h2&gt;
&lt;p&gt;I saw a tweet last year that stuck with me:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&amp;quot;A diagram about the current system is only useful if it&amp;#39;s generated. If it is produced by hand it documents the author&amp;#39;s belief, not the system.&amp;quot; 💯 &lt;a href=&quot;https://t.co/aenK2eRW07&quot;&gt;https://t.co/aenK2eRW07&lt;/a&gt;&lt;/p&gt;&amp;mdash; Marcello Galhardo (@marcellogalhard) &lt;a href=&quot;https://twitter.com/marcellogalhard/status/1632358539858518017?ref_src=twsrc%5Etfw&quot;&gt;March 5, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;That’s exactly what I’d been doing—manually drawing module graphs, watching them go stale, then redrawing them. Even after switching to &lt;a href=&quot;https://mermaid.js.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Mermaid.js&lt;/a&gt; for version control, I was still maintaining them by hand. The tweet forced the question: why not generate them from the source code itself?&lt;/p&gt;
&lt;h2&gt;The Evolution of Tooling&lt;/h2&gt;
&lt;p&gt;I’ve used diagrams for years. Started on whiteboards, moved to &lt;a href=&quot;https://app.diagrams.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;diagrams.net&lt;/a&gt; for remote work, then &lt;a href=&quot;https://excalidraw.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Excalidraw&lt;/a&gt; for cleaner output. The “as-code” tools like Mermaid and &lt;a href=&quot;https://plantuml.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;PlantUML&lt;/a&gt; were an improvement—I could diff them, version them, and check them in.&lt;/p&gt;
&lt;p&gt;But they were still static artifacts I had to remember to update. The tweet made me realize that was a bug, not a feature.&lt;/p&gt;
&lt;h2&gt;Prior Art&lt;/h2&gt;
&lt;p&gt;Before building anything, I looked for existing solutions. &lt;a href=&quot;https://github.com/JakeWharton/SdkSearch/blob/33e4b60af539aef108d1d3193f99ae151ce14733/gradle/projectDependencyGraph.gradle#L1&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Jake Wharton&lt;/a&gt; had a script that parsed Gradle projects and emitted Graphviz DOT files.&lt;/p&gt;
&lt;p&gt;It worked, but added a binary dependency and produced JPGs—useless for diffs and clunky in CI. I wanted something lighter that output plain text and ran anywhere.&lt;/p&gt;
&lt;h2&gt;From Script to Plugin&lt;/h2&gt;
&lt;p&gt;The result is a Gradle plugin that introspects your module dependencies and writes a Mermaid graph directly into your README. No external binaries, no manual steps.&lt;/p&gt;
&lt;p&gt;I started with a &lt;a href=&quot;https://gist.github.com/iurysza/5a902eef98074cab1211344c7e437bbc&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;POC&lt;/a&gt; and eventually published the full plugin to the &lt;a href=&quot;https://plugins.gradle.org/plugin/dev.iurysouza.modulegraph&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Gradle Portal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To get started, apply the plugin and run:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;./gradlew createModuleGraph&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You get a live module diagram that’s readable on GitHub and GitLab:&lt;/p&gt;
&lt;figure style=&quot;text-align:center;&quot;&gt;
  &lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 762px; &quot;&gt;
      &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom: 94%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsTAAALEwEAmpwYAAADHElEQVR42mVU6XrbNhD0O7WJI1EUb1w8wFOHJbtN0vd/heksSCt28mOFTyAw2NmZ3ad9bpBUDoXzOHLdZQZ/JRp/f4zjusq3Q2EZ65roNqxR2LPY5RZPqbJwTY1uHGBqi1TX2Fc8KKFaRFXDgy6AyjfLs5W1UK7GMM+846D438pdYj0l/NHOotQ6rKYbA+CubPCNQLuiQaw9dH/B+e0HKgI9RxG+Rgfs05x3XHhA7iaVAFbrn8oY1G2DejjhQLCq8eiWC9rphNJ18Oc3DJc7TMPMszxEpvQD0HwENNyUDIdphO4mRKXQ0Gi85yMtWq5lO2GfqUBZ1zWp1qS90lWfAB+UDaZlQT2ekJgO/TSFg3L5er+jbCZmyqz7PjwkIaDVBqh/B5TNl/sNmWnxnOpQq5yPpJVCMwwUqaYoLEnXwTJr2zTohmEFZbn+yNDx0O31jijXiKmuAH3d77GLj1A1a9rNyG3HzLoAWvuVRcuMywDoVsBU0YOqgufH5XyGoyi6P2Eg/Zx1XV5uGE8L4kqE6oNNJCMJyXI6ncjOrZTFNnFpEFGxKMn4gkJR94FeYRwzPGB3TKGbDinrKjX0pClgehNSAB0zVtZsPmSaaSUZzjT3yG5QoYZyyLQdgQ0NvODIrhDK19sNLUFzpQKg0O9pcGU2QKFcacX6veLnfz8Rl5ZZjkHNd/U8H9L+hNw0OFPxuKhCSP0kztcrdMjQrYA5Dapo2Lcf34MPpScLKiwh3zRFme/fYf0UlI3zAhnLI/W0nafqPrA8VmYFFEW/RDEFeGFnnHE0PfJ62mIMUbQzhvMNNTNKyjJ0SUUxxAFxUWKXpAR8t40VxVq8/vsP2nEFTOx7DKGXv6QGJQVrhgnPhxjf4gTZ1gzL5Uqh9Er50Sks6kzF/HKlyh1F8L/C+PCI6xf0rKeIUTBE7flyCfbRdrPNx+Hg+EEox8p/BmQcuFdwSEhvu3Y190zfimAPY6ttHsocFNCQKS+lrFn2W6Ru5CRayGLBuMwYaRUBF//Jvcc8/DWFbbDMs0zso+JA/RyyJ/6U1txvEeUf7m4T+39ZEr4sonmCNQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;&gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source srcset=&quot;/static/7ae29048ee909f09a3035016fce1b6b9/577d7/architecture-graph.webp 300w,
/static/7ae29048ee909f09a3035016fce1b6b9/93ccb/architecture-graph.webp 600w,
/static/7ae29048ee909f09a3035016fce1b6b9/68e44/architecture-graph.webp 762w&quot; sizes=&quot;(max-width: 762px) 100vw, 762px&quot; type=&quot;image/webp&quot;&gt;
          &lt;source srcset=&quot;/static/7ae29048ee909f09a3035016fce1b6b9/37b9b/architecture-graph.png 300w,
/static/7ae29048ee909f09a3035016fce1b6b9/ab3c7/architecture-graph.png 600w,
/static/7ae29048ee909f09a3035016fce1b6b9/7d9b4/architecture-graph.png 762w&quot; sizes=&quot;(max-width: 762px) 100vw, 762px&quot; type=&quot;image/png&quot;&gt;
          &lt;img class=&quot;gatsby-resp-image-image&quot; src=&quot;/static/7ae29048ee909f09a3035016fce1b6b9/7d9b4/architecture-graph.png&quot; alt=&quot;It should look something like this&quot; title=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;&gt;
        &lt;/picture&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;It should look something like this&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;It’s a single task that hooks into the build graph without getting in the way. The code is on &lt;a href=&quot;https://github.com/iurysza/module-graph&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GitHub&lt;/a&gt;. CI integration is trivial—just run the task and commit the delta.&lt;/p&gt;
&lt;p&gt;This is my first plugin, so I expect rough edges. Issues and PRs welcome.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[From macro to micro]]></title><description><![CDATA[There’s this interesting idiom in English: to miss the forest for the trees. From
the Merriam-Webster dictionary:
to not understand or…]]></description><link>https://iurysouza.dev/problem-solving/</link><guid isPermaLink="false">https://iurysouza.dev/problem-solving/</guid><pubDate>Wed, 13 Jul 2022 15:30:37 GMT</pubDate><content:encoded>&lt;p&gt;There’s this interesting idiom in English:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;to miss the forest for the trees.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From
the &lt;a href=&quot;https://www.merriam-webster.com/dictionary/miss%20the%20forest%20for%20the%20trees&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Merriam-Webster dictionary&lt;/a&gt;:
&lt;em&gt;to not understand or appreciate a larger situation, problem, etc., because one is considering only a few parts of it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I think this idea translates very well to problem-solving. Sometimes we fail to see the big picture because you’re
trapped
on the small details. Let’s consider for example the case of algorithm coding problems.&lt;/p&gt;
&lt;h1&gt;Algorithm coding problems&lt;/h1&gt;
&lt;p&gt;Whether you agree with solving these as part of a hiring process is beyond the point of this article. But if
you visit a website like &lt;a href=&quot;https://leetcode.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Leet Code&lt;/a&gt; or &lt;a href=&quot;https://www.hackerrank.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;HackerRank&lt;/a&gt; you can easily
spot that there are multiple categories of problems.&lt;/p&gt;
&lt;p&gt;There are problems with trees, graphs, sliding windows, etc. Each of these categories represent a sets of
problems which you can solve by applying a certain algorithm.&lt;/p&gt;
&lt;p&gt;These algorithms are, at the highest level, the main tool you’re going to use to solve a problem.
It is only natural that first you need to have these tools on your tool belt to be able to use them.&lt;/p&gt;
&lt;h1&gt;Your Tool belt&lt;/h1&gt;
&lt;p&gt;Knowing when to use an algorithm and knowing how to implement it should be the first step.
That seems kinda obvious, right?
But, the point is that if you’re solving a sliding window problem, creating a sliding window, needs to be trivial for
you.&lt;/p&gt;
&lt;p&gt;It can be really frustrating to forget about a certain check, or getting some kind of out of bounds exception because
you can’t remember what a sliding window looks like.
When you’re solving a problem, a sliding window has to be just a tool. When you decide to use it, it’s gotta be ready to
go.&lt;/p&gt;
&lt;h1&gt;Knowing your tool&lt;/h1&gt;
&lt;p&gt;The other thing is understanding how to use that tool. Think of it as just any other tool… like a drill.
It’s got some buttons and some switches that can alter its behavior. You can also use different bits depending on
surface.&lt;/p&gt;
&lt;p&gt;Having a drill by itself is not enough. You have to know what can be changed or configured on it for
different scenarios.&lt;/p&gt;
&lt;p/&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 833px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/86becb53ec7bd43330dc63f259e535be/ad42d/drill.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.66666666666666%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAcDBQn/xAAVAQEBAAAAAAAAAAAAAAAAAAACA//aAAwDAQACEAMQAAAB3DWTpnc7IAU//8QAGxAAAgIDAQAAAAAAAAAAAAAAAQUCBgMEFBH/2gAIAQEAAQUCsLrFXk9etzVy7ifQxXarTS5MXRGIiP/EABwRAQEBAAEFAAAAAAAAAAAAAAIBEgADESEiQv/aAAgBAwEBPwFFWil05cVkkuzlG9Nd/n23KcqMjznRfP/EABsRAQEBAAIDAAAAAAAAAAAAAAECEQASISJC/9oACAECAQE/AZqQoYKanB19HtKUY5vhl0TrT9Yzz//EACUQAAICAQQBAwUAAAAAAAAAAAECAwQFABESIRMUMUEyQmGR8P/aAAgBAQAGPwLIZizFPJXx1We3KtZPJKYoF5ELy2UO3QX6u/ggEi7ichgLGEiXF1cvibFqVxPfrSWDXn9VTlgry0ZYHaBXgcE82kALKiO49t/nj2N9W8fcV3q3q71rCLLJEWikHFgrxsrRkj7kKt+derZUNjwtXEwQLJ4WkExjLdkr5BzAPQO5271sP79ba//EABwQAQADAAIDAAAAAAAAAAAAAAEAESExUUGBkf/aAAgBAQABPyE5yWkETgBunKhmT+SO7/qY8zYldDbgaD1GdI3jqvTjvgwzEmIX7KP0rcVAP0ote2gv1P/aAAwDAQACAAMAAAAQU8//xAAXEQEBAQEAAAAAAAAAAAAAAAABERAh/9oACAEDAQE/EDwpsXK0tIQQrOb/xAAXEQEBAQEAAAAAAAAAAAAAAAABERAh/9oACAECAQE/EE9BeLxLFBTcYY3/xAAaEAEBAQEAAwAAAAAAAAAAAAABESEAEDFB/9oACAEBAAE/ELuutWBc3xS6sDGWOCOyN4qaZqUWT7pC5YxFN5B/KKBiqLhFgJcB5/MCJWzQBcSWGqFEtUgCwwD4d//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/86becb53ec7bd43330dc63f259e535be/577d7/drill.webp 300w,
/static/86becb53ec7bd43330dc63f259e535be/93ccb/drill.webp 600w,
/static/86becb53ec7bd43330dc63f259e535be/7c7b3/drill.webp 833w&quot;
              sizes=&quot;(max-width: 833px) 100vw, 833px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/86becb53ec7bd43330dc63f259e535be/622f1/drill.jpg 300w,
/static/86becb53ec7bd43330dc63f259e535be/58041/drill.jpg 600w,
/static/86becb53ec7bd43330dc63f259e535be/ad42d/drill.jpg 833w&quot;
            sizes=&quot;(max-width: 833px) 100vw, 833px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/86becb53ec7bd43330dc63f259e535be/ad42d/drill.jpg&quot;
            alt=&quot;drill&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;text-align: center;&quot;&gt; a drill &lt;/div&gt;
&lt;p/&gt;
&lt;p&gt;For example, a sliding window can have a dynamic, or static width.
Or maybe you need to keep track of different things while you’re moving across the dataset. Knowing how to work with
the tool is key.&lt;/p&gt;
&lt;h1&gt;Sharpening your tool&lt;/h1&gt;
&lt;p&gt;The next thing is the language and its data structures and idioms. You have to be comfortable with the language
that you’re going to work with. Compilation errors shouldn’t catch off guard you. You have to make sure that you
know how to use its data types. Working with Arrays, and maps should really not be a problem.&lt;/p&gt;
&lt;h1&gt;Planning and Zooming&lt;/h1&gt;
&lt;p&gt;If you have these out of the way, then you can really focus on what the problem is about.
In my experience, ideally you should to have a plan before you start coding. Drawing, sketching, writing, etc
are all useful at this point.
This way you can piece together each part of the idea and gradually zoom in, from the bigger picture, to the small
details
of the implementation.&lt;/p&gt;
&lt;p&gt;Then, you can validate your assumptions at each step of the way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is my sliding window iterating over the dataset correctly?&lt;/li&gt;
&lt;li&gt;Is this validation correct?&lt;/li&gt;
&lt;li&gt;Is the content of that data structure correct?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But, if you start having issues with your sliding window implementation because you forgot how to keep track of the
pointers, or if compilation errors keep popping up, or you can’t add new items to your hash-map,
then this will take up your energy, and you won’t be able to be to focus on the actual problem-solving.&lt;/p&gt;
&lt;p&gt;Since you can only keep a certain amount of subjects in your working memory, it’s important to use it efficiently.
Avoiding constant changes in this zoom level helps to manage the amount of information you’re going to have to work
with.&lt;/p&gt;
&lt;h1&gt;Wrap up - Keeping the forest in sight&lt;/h1&gt;
&lt;p&gt;Keeping context switching to a minimum is a key part of problem-solving for me. I usually trip over myself
when I’m trying to think through something only to find out that some basic assumptions were wrong. Or if I keep getting
weird compilation errors, or if my hash-map is not working as I thought.
When you start to jump around fixing different parts of the implementation, at different “zoom levels”,
you lose sight of the forest. For that reason, &lt;em&gt;when studying&lt;/em&gt;, I think it’s important to check each of these stages
before jumping into a problem.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Nesting React Components in Native UI]]></title><description><![CDATA[React Native has support for creating Native UI components which are implemented using the native ui frameworks, and
exposed as react-native…]]></description><link>https://iurysouza.dev/react-native-nested-views/</link><guid isPermaLink="false">https://iurysouza.dev/react-native-nested-views/</guid><pubDate>Tue, 05 Jul 2022 18:46:37 GMT</pubDate><content:encoded>&lt;p&gt;React Native has support for creating Native UI components which are implemented using the native ui frameworks, and
exposed as react-native components. This is the most common use case, but there’s also a not so common one, which
allows you to nest React Native components inside the native UI.&lt;/p&gt;
&lt;p&gt;Now, let’s skip the whole discussion about whether you should do that or not.
Since there’s not a lot of info about this on the web, the main point of this article is to give you a brief overview
of what nesting react components looks like in practice and how to implement that on both platforms.&lt;/p&gt;
&lt;h2&gt;Native UI Components in React-native&lt;/h2&gt;
&lt;p&gt;I’m breaking down this feature in two main use cases:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Basic Native UI component&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You have a native ui component which can be shown inside a React Native app, side-by-side a react component.&lt;/p&gt;
&lt;p&gt;This one is well covered, you can follow the &lt;a href=&quot;https://reactnative.dev/docs/native-components-ios&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;official docs&lt;/a&gt; to
implement it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nesting React Native components in Native UI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In addition to the basic use case, this &lt;code class=&quot;language-text&quot;&gt;native-ui&lt;/code&gt; also supports having react components being laid out inside it.&lt;/p&gt;
&lt;p&gt;Let’s take a second to visualize what we want to build:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8b5afdab42ac619a5a3751a9117d6ff1/d6a86/rn-article.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 63.33333333333333%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACKklEQVR42oWTW2/TQBCF/c95A34CQhAJCcELAgmJlkZNURsuJaSQuo0bx7k4FzuOHSd2Nk6qtPXHrtPQixT6cDS7s96ZOT5ntXgGEwHjKYS3EMm8OvOjq+xc7RXC6f+h2UGCM14ymFz+SwZxStO7oDm44NAU1N0lHT+lE1wyih8oeNCwsfwprUBkE4xFijeB12Wd50f75H4XyR0VeXv8k62qzUhemlyzypiJm7WC9qMWy0mWmP0F1W6SUfRlwVz5K8+NbV4aBV4Yu+SMPO9OTnGGSxw/wZZDdEczeqMki2towTRlGKX4kkogu8dJiisL7pyeUGn/QrdP0DtVjrtVdipn1O2IrjvNCrblJSec0w5izEFAZyTQmi0PdzjDGQg6vTHN1hDdHPJqr8CbbwZlq8F84hL6dUqWjRerpndpRmIlYEb5qFyl8LlA/lMeXbewmgOsfsL7cklOdMj32gGVVoFSY5uDehs3VI5IpTgSMiohVFxDi+cyESv1rohk59k5dOWlkmVwKeqI2COSE85jXxZ1JMU0m2SjyqrTeqPWqnt/DF8Mk7Zj0ur3afQ6WN0WRbMn7bVSdmPB2xtlCWVwP4JnRY9H2z0e5x2e7no82R3yoRJxe4AHCyqo7kr1/VpC4WzB1p+QjyWXvdqCcvt8xULcfHt/Wm1TJ6XcNFFR2sgT2NIBprPA6M05lX49k1G3Z9K78zuvZ2NB9dEaoVjFYJ27/j3qid5/in8BbCvKfuFYAc0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/8b5afdab42ac619a5a3751a9117d6ff1/577d7/rn-article.webp 300w,
/static/8b5afdab42ac619a5a3751a9117d6ff1/93ccb/rn-article.webp 600w,
/static/8b5afdab42ac619a5a3751a9117d6ff1/1aa16/rn-article.webp 1200w,
/static/8b5afdab42ac619a5a3751a9117d6ff1/daa23/rn-article.webp 1800w,
/static/8b5afdab42ac619a5a3751a9117d6ff1/54426/rn-article.webp 1884w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/8b5afdab42ac619a5a3751a9117d6ff1/37b9b/rn-article.png 300w,
/static/8b5afdab42ac619a5a3751a9117d6ff1/ab3c7/rn-article.png 600w,
/static/8b5afdab42ac619a5a3751a9117d6ff1/5c837/rn-article.png 1200w,
/static/8b5afdab42ac619a5a3751a9117d6ff1/2f4db/rn-article.png 1800w,
/static/8b5afdab42ac619a5a3751a9117d6ff1/d6a86/rn-article.png 1884w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/8b5afdab42ac619a5a3751a9117d6ff1/5c837/rn-article.png&quot;
            alt=&quot;rn article&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;So, what are we looking at?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We have a React Native App which has a &lt;code class=&quot;language-text&quot;&gt;Native UI Component&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This &lt;code class=&quot;language-text&quot;&gt;Native UI Component&lt;/code&gt; has a native view, which acts as &lt;code class=&quot;language-text&quot;&gt;slot&lt;/code&gt; in its view hierarchy where we place
the &lt;code class=&quot;language-text&quot;&gt;nested react-native component&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally, we have a simple &lt;code class=&quot;language-text&quot;&gt;React Native Component&lt;/code&gt; as a sibling of the &lt;code class=&quot;language-text&quot;&gt;Native UI Component&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From the react-native side, this could look like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;tsx&quot;&gt;&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; requireNativeComponent &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;react-native&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; YourNativeComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requireNativeComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;YourNativeComponent&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;YourNativeComponent&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;nativeID&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;aWildNativeID&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Button&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Nested React native Button&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;YourNativeComponent&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Button&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;React native Button&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’ll revisit this later, but for now, let’s take a look at the native code.&lt;/p&gt;
&lt;h2&gt;Native Changes&lt;/h2&gt;
&lt;p&gt;Let’s quickly go over the basic building blocks of native-ui integration with react.&lt;/p&gt;
&lt;p&gt;On &lt;strong&gt;android&lt;/strong&gt; we have the &lt;code class=&quot;language-text&quot;&gt;ViewGroupManager&lt;/code&gt; class, which you need to extend from to create a native ui component.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; YourNativeComponentManager &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ViewGroupManager&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;YourComponentView&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createViewInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ThemedReactContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; FrameLayout &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;YourComponentView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; YourComponentView&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; child&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; View&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  	  &lt;span class=&quot;token function&quot;&gt;TODO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Add child to view hierarchy&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;YourNativeComponent&quot;&lt;/span&gt;&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;On &lt;strong&gt;iOS&lt;/strong&gt; that looks a bit different:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;swift&quot;&gt;&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token attribute atrule&quot;&gt;@objc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;YourNativeComponentManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;YourNativeComponentManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RCTViewManager&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UIView&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;YourNativeComponentView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;YourNativeComponentView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UIView&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;insertReactSubview&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token omit keyword&quot;&gt;_&lt;/span&gt; subview&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UIView&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; at atIndex&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// TODO: add subview to view hierarchy&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When the react-native side starts nesting components, as we showed before, you’ll start receiving calls to the
&lt;code class=&quot;language-text&quot;&gt;addView&lt;/code&gt; method on &lt;strong&gt;android&lt;/strong&gt; and to the &lt;code class=&quot;language-text&quot;&gt;insertReactSubview&lt;/code&gt; on &lt;strong&gt;iOS&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;These methods will allow you to actually lay out the views on your component’s view hierarchy.&lt;/p&gt;
&lt;h2&gt;Advanced Layout Strategy&lt;/h2&gt;
&lt;p&gt;If you plan to nest multiple kinds of components in different places of your view, you will need to change things a bit.&lt;/p&gt;
&lt;p&gt;Imagine you have &lt;code class=&quot;language-text&quot;&gt;react-component A&lt;/code&gt; and a &lt;code class=&quot;language-text&quot;&gt;react-component-B&lt;/code&gt; and they need to be placed in different places in your
view hierarchy.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1200px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/43213aca964a57e9e448a7fe735bad26/d6a86/rn-article-nested-views.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.666666666666664%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABs0lEQVR42nVSXW/aQBDk//+BPlWKqkp9iZT0rQ1FimSlJW2IwLjYYMCQnPHZPmMbG/NlPNk7k6hUysNo73N3Z2Yb0Qp4xTIjUBSnvYwJnfHoCD+u1DpKK/z75380VCKCn5TQWQDbX4HHe6Q5MA9L3FgOLrs6rnQDXw2T3qyQruvicXYe3xLKA29Z4s84xtDdwGIFdXSEE+zwqafhwmrh498WPvS+44c1hYhKMFGARQXcaAN3WUMlFEQhTGpw1SnF5VFRM59XaPY7VOgRlmtDn5nQBlMs/A2eggxzkalE0yDBk8jrhA7fwWQb2NSV2R1hxNYYLrYYsxz3fQdf7h5w/WAgCAxMnn/hcc6RFSDalZIlyaEkkGtJW3UoxRdxCW84pU5LhKmUoYJNxQynDxH2oVm/cdX5ie7ch6B7Jg5YkEw8PSq5XJJBMmucuVyc3E3ryra3Q8+xwbiD24GJz1obrY4JFh5Igi0cvkVPu1dsxt4eM39/nvB1JERaGzXhe9yYa3wb5GRGhiahPUppfCpVUCIK14qNpCz/NN6bJzlKHjltLQ6kKcGTIGrSsNOMqnnNcbZ/AbjjoTuS4oomAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/43213aca964a57e9e448a7fe735bad26/577d7/rn-article-nested-views.webp 300w,
/static/43213aca964a57e9e448a7fe735bad26/93ccb/rn-article-nested-views.webp 600w,
/static/43213aca964a57e9e448a7fe735bad26/1aa16/rn-article-nested-views.webp 1200w,
/static/43213aca964a57e9e448a7fe735bad26/daa23/rn-article-nested-views.webp 1800w,
/static/43213aca964a57e9e448a7fe735bad26/54426/rn-article-nested-views.webp 1884w&quot;
              sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/43213aca964a57e9e448a7fe735bad26/37b9b/rn-article-nested-views.png 300w,
/static/43213aca964a57e9e448a7fe735bad26/ab3c7/rn-article-nested-views.png 600w,
/static/43213aca964a57e9e448a7fe735bad26/5c837/rn-article-nested-views.png 1200w,
/static/43213aca964a57e9e448a7fe735bad26/2f4db/rn-article-nested-views.png 1800w,
/static/43213aca964a57e9e448a7fe735bad26/d6a86/rn-article-nested-views.png 1884w&quot;
            sizes=&quot;(max-width: 1200px) 100vw, 1200px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/43213aca964a57e9e448a7fe735bad26/5c837/rn-article-nested-views.png&quot;
            alt=&quot;rn article nested views&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;When &lt;code class=&quot;language-text&quot;&gt;addView&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;insertReactSubview&lt;/code&gt; are called, you’ll just get a new view, and you have to do something with it (put
it somewhere).&lt;/p&gt;
&lt;h3&gt;Identifying react subviews:&lt;/h3&gt;
&lt;p&gt;To identify each view, you can tag them on the react-native side with the &lt;code class=&quot;language-text&quot;&gt;nativeID&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;This way, when you get that view on the native side, you can use that to figure out where should you place it.&lt;/p&gt;
&lt;p&gt;On &lt;strong&gt;android&lt;/strong&gt;, you need to use the react’s android library to do that via the
&lt;code class=&quot;language-text&quot;&gt;ReactFindViewUtil.findView(view, &quot;nativeId&quot;)&lt;/code&gt;. It will help you identify if a particular view has the
given &lt;code class=&quot;language-text&quot;&gt;nativeID&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On &lt;strong&gt;iOS&lt;/strong&gt; that’s a bit easier. You have access to the view’s &lt;code class=&quot;language-text&quot;&gt;nativeID&lt;/code&gt; directly via &lt;code class=&quot;language-text&quot;&gt;subview.nativeID&lt;/code&gt; property.&lt;/p&gt;
&lt;h3&gt;Laying Them Out:&lt;/h3&gt;
&lt;p&gt;To lay out these components accordingly, what I suggest is limiting them to a slot on your native implementation.
This way, the native side can stay in control of its own ui. You assign each of these possible subviews a slot on
your view hierarchy.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Kotlin Multiplatform under the surface]]></title><description><![CDATA[The mobile development community always had many alternatives for creating cross-platform apps. Since the days of
webview-based solutions we…]]></description><link>https://iurysouza.dev/kotlin-multiplatform-under-the-surface/</link><guid isPermaLink="false">https://iurysouza.dev/kotlin-multiplatform-under-the-surface/</guid><pubDate>Wed, 18 Aug 2021 08:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The mobile development community always had many alternatives for creating cross-platform apps. Since the days of
webview-based solutions we’ve come a long way, but we still have many alternatives. The most recent one is the Jetbrains
led, Kotlin Multiplatform Mobile project.&lt;/p&gt;
&lt;p&gt;If you’re curious about how does it stand against the competition and what are its pros and cons, this brief overview
may help you get started.&lt;/p&gt;
&lt;h2&gt;Cross-platform vs Multiplatform&lt;/h2&gt;
&lt;p&gt;As you may have noticed, Jetbrains calls its solution: Kotlin &lt;strong&gt;Multiplatform&lt;/strong&gt; Mobile. This might get overlooked by a
lot of people, me included, up until I decided to write this down. But then it hit me: what’s the difference between
Cross-platform and Multiplatform?&lt;/p&gt;
&lt;p&gt;After some research, I got the answer.
From the user’s point of view, this is plain simple. In short: there’s no difference.
Multiplatform and Cross-platform implementations achieve the same thing. It simply means that the application will run
on - you guessed it. Multiple platforms.&lt;/p&gt;
&lt;p&gt;Since we’re focusing on mobile dev, Multiplatform means Android and iOS. But this can translate to web, desktop…
whatever. Just… multiple platforms.
It doesn’t say anything about &lt;strong&gt;how&lt;/strong&gt; the app was created. If you have a code-base for an app written in Kotlin
targeting android and another written in swift targeting iOS, you have a multiplatform app.&lt;/p&gt;
&lt;p&gt;Now, cross-platform is different. Cross-platform means that the same code you wrote will run on, for example, android
and iOS. This is usually done via some kind of runtime that needs to be shipped to the hosting platform that enables
that code to be &lt;em&gt;interpreted&lt;/em&gt; for that particular environment it is running on.&lt;/p&gt;
&lt;h2&gt;So what about Kotlin Multiplatform?&lt;/h2&gt;
&lt;p&gt;The main idea behind Kotlin Multiplatform is &lt;strong&gt;code-sharing&lt;/strong&gt;. If you enter the home page, you’ll see them mentioning
how you can write the code once and have it running on both platforms. So -you may be asking, isn’t that
cross-platform… like Flutter or React? What gives?&lt;/p&gt;
&lt;p&gt;The main advantage of Kotlin Multiplatform is that it delivers &lt;strong&gt;native code&lt;/strong&gt; for multiple target
platforms/architectures by taking the same code as input.
It is able to do this because of the &lt;strong&gt;Kotlin/Native Compiler&lt;/strong&gt;, so to understand how Kotlin Multiplatform works we
first need to understand what is Kotlin/Native.&lt;/p&gt;
&lt;h2&gt;Kotlin/Native&lt;/h2&gt;
&lt;p&gt;Quoting from Kotlin’s landing page:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Kotlin/Native is a technology for compiling Kotlin code to native binaries, which can run without a virtual machine.
It is an LLVM based backend for the Kotlin compiler and a native implementation of the Kotlin standard library.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/flnyeb40col3h8mvye69.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;There’s a lot to unpack here. Let’s take the first sentence in:&lt;/p&gt;
&lt;p&gt;So Kotlin/Native will give us native binary executables that can run on an OS using no virtual machine.&lt;/p&gt;
&lt;p&gt;A binary is the machine code, ready to be run on the specified target architecture. So it’s the machine code for, for
example, the iOS targeting a specific CPU architecture, like &lt;code class=&quot;language-text&quot;&gt;arm64&lt;/code&gt; (which is the official iPhone architecture name)&lt;/p&gt;
&lt;p&gt;Then it goes on to mention an &lt;strong&gt;LLVM based backend for the Kotlin compiler&lt;/strong&gt;. This one is dense.&lt;/p&gt;
&lt;h3&gt;LLVM&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://llvm.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;The LLVM Compiler Infrastructure Project&lt;/a&gt; is probably the biggest open-source compiler project that
exists to build native binaries. Languages such as C, C++, Haskell, Rust &amp;#x26; Swift compile into native binaries through
LLVM.&lt;/p&gt;
&lt;p&gt;Ok, fine… but what about the backend? More specifically, what is a backend for a compiler?&lt;/p&gt;
&lt;h3&gt;The Kotlin Compiler, frontends, and backends&lt;/h3&gt;
&lt;p&gt;The Kotlin compiler first compiles your Kotlin source code into an &lt;em&gt;Intermediate Representation&lt;/em&gt;, or &lt;em&gt;IR&lt;/em&gt;. This is what
they call the &lt;em&gt;Frontend&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Then, the LLVM compiler takes this IR as input and generates machine code for several supported CPU architecture (like
the aforementioned arm64). This is the compiler backend.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6cgqfc6h6ryysfio80wn.png&quot; alt=&quot;Alt Text&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Batteries included&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;(…) and a native implementation of the Kotlin standard library.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This last part refers to the stdlib. Kotlin multiplatform also includes a sound standard library. This allows us to get
up and running with it. So when writing common code, we’ll be using things like &lt;code class=&quot;language-text&quot;&gt;kotlin.String&lt;/code&gt; instead
of &lt;code class=&quot;language-text&quot;&gt;java.lang.String&lt;/code&gt;. The &lt;em&gt;Compiler Backend&lt;/em&gt; will automatically map the IR a &lt;code class=&quot;language-text&quot;&gt;Kotlin.String&lt;/code&gt; into the correct type.&lt;/p&gt;
&lt;p&gt;If you think about it, since the native side doesn’t have access to, for example, &lt;code class=&quot;language-text&quot;&gt;List&lt;/code&gt;, this means you would have to
roll your own when targeting that platform. Not cool.
So the Kotlin team built this to enable us to hit the ground running when targeting native. They also did that for
things like IO, networking, and more - all of which are included.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Kotlin.String   ----jvm-----&gt; java.lang.String
                -----js-----&gt; string
                ---native---&gt; KString&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Long story short&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;The Kotlin Compiler compiles code into an Intermediate Representation with the Compiler Frontend&lt;/li&gt;
&lt;li&gt;Then, it takes this &lt;em&gt;IR&lt;/em&gt; and sends it to the LLVM based Compiler Backend.&lt;/li&gt;
&lt;li&gt;Kotlin has a stdlib that covers most basic use-cases, like IO and networking which is compiled to the appropriate
target representation.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Not Cross-platform, but truly Multiplatform&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Write once, run anywhere (with the jve).&lt;/em&gt; &gt; &lt;em&gt;-java&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Kotlin, probably:
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ms3j71np9sy75jovxnrp.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Kotlin Multiplatform takes advantage of its great tooling paired with the amazing LLVM project to create true native
code that you only need to write once.&lt;/p&gt;
&lt;p&gt;It is not Cross-platform because it doesn’t need a runtime to be able to run anywhere. It is just plain native code.&lt;/p&gt;
&lt;p&gt;Cool, right?
I would say that Kotlin is able to take that direction because of its tooling/architecture and the stage that LLVM
project has achieved. Let’s see what the future holds for this tech.
There are certainly many challenges that we haven’t discussed here, but anyways, the foundation of the tech looks pretty
interesting to me.&lt;/p&gt;
&lt;p&gt;There’s this &lt;a href=&quot;https://www.youtube.com/watch?v=Dul17VSiejo&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;talk&lt;/a&gt; from KotlinConf18 by Kevin Galligan where he goes on to
make the case for this multiplatform idea and why he thinks this is &lt;em&gt;the future&lt;/em&gt;.
I also recommend this great [post](&lt;a href=&quot;https://blog.londogard.com/gradle/kotlin/workshop/multiplatf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://blog.londogard.com/gradle/kotlin/workshop/multiplatf&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[A Continuous Integration approach for Kotlin projects]]></title><description><![CDATA[Continuous integration Continuous integration is one of the words that you often hear nowadays. I remember not really getting it when I…]]></description><link>https://iurysouza.dev/continuous-integration-approach-for-kotlin/</link><guid isPermaLink="false">https://iurysouza.dev/continuous-integration-approach-for-kotlin/</guid><pubDate>Sun, 24 Jan 2021 23:46:37 GMT</pubDate><content:encoded>&lt;h1&gt;Continuous integration&lt;/h1&gt;
&lt;p&gt;Continuous integration is one of the words that you often hear nowadays. I remember not really getting it when I first
read about it. I think that most people don’t get it right away. It’s one of those things that you don’t see the benefit
before you need it, like an insurance or a sun lotion.&lt;/p&gt;
&lt;p&gt;So, let’s try to tackle this one once again. What is &lt;strong&gt;Continuous Integration&lt;/strong&gt;?
Integrate what? And why should we continually do it?&lt;/p&gt;
&lt;p&gt;As you can see, there’s clearly something missing in this term: the subject. Maybe it should be called Continuous **
CODE** Integration, but that may not sound so appealing.
Now you might be wondering what this &lt;em&gt;integration&lt;/em&gt; is. Here it refers to code merging. Let’s jump to a full definition:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Continuous Integration (CI) is a phase in the software development cycle where code from different team members or
different features are integrated together. This usually involves merging code (integration), building the application
and carrying out basic tests all within an ephemeral environment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Successful Continuous integration means new code changes to an app are regularly built, tested, and merged to a shared
repository. It aims to improve the quality of software, and to reduce the time taken to deliver it, by replacing the
traditional practice of applying quality control after completing all development.&lt;/p&gt;
&lt;p&gt;It’s also a solution to the problem of having too many branches of an app in development at once that might conflict
with each other.
The main benefits here is making sure that the code quality doesn’t decrease while keep this process small and
automated.&lt;/p&gt;
&lt;p&gt;You can check my DevOps series articles, where I go deeper about some concepts behind CI and automation.
[A Primer]&lt;a href=&quot;https://github.com/iurysza/thoughtbox/devops-primer/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/iurysza/thoughtbox/devops-primer/&lt;/a&gt;)
&lt;a href=&quot;https://github.com/iurysza/thoughtbox/devops-the-first-way/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;The First Way&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Pipelines&lt;/h1&gt;
&lt;p&gt;The &lt;strong&gt;pipeline&lt;/strong&gt; is another loaded term. We lend its usage from the lean manufacturing process. The basic intuition is
that each software change should go through an automated code quality verification assembly line instead of using a
manual, artisan like approach to review the changes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://mir-s3-cdn-cf.behance.net/project_modules/disp/c0843a20257383.562e8608353ac.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To achieve that we need a clear process to guarantee software quality after every change.
CI pipelines can vary a lot depending on tools, type of project and many other factors, but a common flow has these
steps.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pushing to the code repository&lt;/li&gt;
&lt;li&gt;Running Static code analysis&lt;/li&gt;
&lt;li&gt;Unit testing&lt;/li&gt;
&lt;li&gt;Manual code revision&lt;/li&gt;
&lt;li&gt;Merge code on the main branch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/0*Ibsu7Nvvd9gyhHxO.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1&gt;An android library example&lt;/h1&gt;
&lt;p&gt;CI/CD tools can help a team automate their development, deployment, and testing. Some tools specifically handle the
integration (CI) side, some manage development and deployment (CD), while others specialize in continuous testing or
related functions.&lt;/p&gt;
&lt;p&gt;One of the best known open source tools for CI/CD is the automation server Jenkins. Jenkins is designed to handle
anything from a simple CI server to a complete CD hub.&lt;/p&gt;
&lt;p&gt;You can even use GitHub with Jenkins, via the &lt;a href=&quot;https://github.com/jenkinsci/ghprb-plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ghprb-plugin&lt;/a&gt; to set up a
pipeline workflow to be triggered after a Pull Request is opened.&lt;/p&gt;
&lt;p&gt;Let’s take a look at how this flow works on an android library project.&lt;/p&gt;
&lt;h2&gt;Our Pipeline&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://dl3.pushbulletusercontent.com/aI7mb8Wt1NnUE9Dl4EWnOEldlWmWvSwK/image.png&quot; alt=&quot;&quot;&gt;
This is an overview of our final pipeline. Now let’s see what each &lt;em&gt;stage&lt;/em&gt; does.&lt;/p&gt;
&lt;h2&gt;Opening a Pull Request&lt;/h2&gt;
&lt;p&gt;This is the starting point. Developers should open a pull request where they explain the context of ther code changes.
Every project have their own pull request process and that should be clear to everybody contributing on to the
code-base.
This pull request should trigger a CI build. While that happens, other developers can contribute to the PR making
questions and reviewing the code.&lt;/p&gt;
&lt;h2&gt;Merge&lt;/h2&gt;
&lt;p&gt;Your integration server will then checkout the code on your branch and merge with the main branch. If there are issues
here, your build will fail and that is reported directly on the PR.&lt;/p&gt;
&lt;h2&gt;Static analysis&lt;/h2&gt;
&lt;p&gt;In this pipeline this stage does two things using the same tool:&lt;/p&gt;
&lt;h4&gt;Code Review Automation&lt;/h4&gt;
&lt;p&gt;To improve the code review process you can automate some parts of it. To do that you can
use &lt;a href=&quot;https://danger.systems/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Danger&lt;/a&gt;. This tool “provides another logical step in your build, through this &lt;code class=&quot;language-text&quot;&gt;Danger&lt;/code&gt; can
help &lt;em&gt;lint&lt;/em&gt; your rote tasks in daily code review.” This way you can codify your team norms. E.G.: PRs with no
description or with no tests are instantly reported back directly on the PR, speeding up the review process.&lt;/p&gt;
&lt;img width=&quot;75%&quot; src=&quot;https://danger.systems/images/danger-screenshot-074f084c.png&quot;&gt;
&lt;h4&gt;Actual static analysis&lt;/h4&gt;
&lt;p&gt;Static analysis is done on the code base of the application without the need to run the software. The goal here is to
ensure the code doesn’t have bugs and conforms to standard formatting and styling. You might want to check
this &lt;a href=&quot;link&quot;&gt;article&lt;/a&gt; I’ve written about it with my approach for Kotlin projects.&lt;/p&gt;
&lt;p&gt;Ideally, developers should have fast feedback for issues during this step, so they can start fixing them ASAP.
To do that, I’ve forked the &lt;a href=&quot;github.com/iurysza/danger-checkstyle_format&quot;&gt;danger-checkstyle-format&lt;/a&gt; plugin and added
some changes to enable reporting issues from both &lt;code class=&quot;language-text&quot;&gt;ktlint&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;detekt&lt;/code&gt; directly on the PR, making developer iteration
faster. Check it out.&lt;/p&gt;
&lt;h2&gt;Build&lt;/h2&gt;
&lt;p&gt;In this step we use a gradle task to build our code.&lt;/p&gt;
&lt;h2&gt;Test&lt;/h2&gt;
&lt;p&gt;Here we run all unit tests, also with gradle.&lt;/p&gt;
&lt;h2&gt;Publish reports&lt;/h2&gt;
&lt;p&gt;This step happens after the pipeline has finished. It runs &lt;a href=&quot;https://plugins.jenkins.io/jacoco/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;jacoco jenkins plugin&lt;/a&gt;,
a java code coverage tool to create coverage reports, then we store the test results in Jenkins. This provides a way for
us to track code coverage and investigate flaky tests later on.&lt;/p&gt;
&lt;h2&gt;Bonus&lt;/h2&gt;
&lt;p&gt;You can also add some basic commands to your Jenkins pipeline, like the &lt;code class=&quot;language-text&quot;&gt;rebuild&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;rerun-tests&lt;/code&gt; commands. To help you
work around flakiness on your project’s build or testing stages.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The benefits you get from having a CI pipeline in place pile up like Loan Shark interests. I strongly advise that you
and your team consider creating one. If you already have one, I’d love to hear some other stages or ideas you’ve added
to it. Please share in the comments =)&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Publishing an obfuscated kotlin library]]></title><description><![CDATA[Being an android developer, I’ve heard about code obfuscation pretty early in my career. Since it’s preconfigured in
android’s gradle plugin…]]></description><link>https://iurysouza.dev/publishing-an-obfuscated-kotlin-library/</link><guid isPermaLink="false">https://iurysouza.dev/publishing-an-obfuscated-kotlin-library/</guid><pubDate>Sun, 27 Dec 2020 23:46:37 GMT</pubDate><content:encoded>&lt;p&gt;Being an android developer, I’ve heard about code obfuscation pretty early in my career. Since it’s preconfigured in
android’s gradle plugin (it’s actually proguard under the hood), but you don’t really mess with it unless you want to.
On the other hand, when you’re developing a java/kotlin library and you want to use code obfuscation, things get a
little different. In this article I wanted to give a brief overview of my strategy for publishing obfuscated kotlin
libraries.&lt;/p&gt;
&lt;h1&gt;Code obfuscation&lt;/h1&gt;
&lt;p&gt;If you’re not sure what that means, code obfuscation is the modification of the source code to make it unintelligible,
meaning it is &lt;em&gt;impossible&lt;/em&gt; for a third-party to understand.
It’s makes for a great AppSec initiative and often takes care of the bare minimum security you can have when publishing
a source code out in they wild. By making the code hard to reverse engineer, you ensure that your library’s intellectual
property is guarded against security threats, unauthorized access, and discovery of application vulnerabilities.&lt;/p&gt;
&lt;h1&gt;Proguard&lt;/h1&gt;
&lt;p&gt;ProGuard is the most popular optimizer for Java bytecode and it also provides obfuscation for the names of classes,
fields and methods.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.guardsquare.com/files/media/guardsquare2016/Website/ProGuard/ProGuard_build_process_b.png&quot; alt=&quot;proguard-pipeline&quot;&gt;&lt;/p&gt;
&lt;p&gt;Proguard is basically a command line tool that takes input jars (aars, wars, ears, zips, apks, directories) and then
performs shriking, optimization, obfuscation and preverification to output a new jar.&lt;/p&gt;
&lt;p&gt;Here we’re just interested in the obfuscation step which will rename the classes, fields, and methods using short
meaningless names making it really hard to read the code.&lt;/p&gt;
&lt;h1&gt;An obfuscation strategy&lt;/h1&gt;
&lt;h3&gt;Choosing our tools&lt;/h3&gt;
&lt;p&gt;There are probably many ways to do this, but since I have a mobile development background, we’ll be doing this using
proguard’s gradle plugin, which will make things are little more simple, specially during the &lt;em&gt;publication&lt;/em&gt; part. Also,
I’ll use gradle’s groovy dsl, just because I like its convenience better than kotlin’s now (for gradle scripts). I may
need another article to justify that, but, bear with me. This is how we integrate proguard’s gradle plugin:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;buildscript &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    repositories &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;jcenter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    dependencies &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        classpath &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.guardsquare:proguard-gradle:7.0.1&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We now need to use it to obfuscate our library’s &lt;code class=&quot;language-text&quot;&gt;jar&lt;/code&gt;. To do that we’re going to use gradle’s syntax to create a task
that does just that.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token function&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;obfuscateArtifact&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ProGuardTask&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dependsOn&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; jar&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s dissec the task signature for a second. Here we’re declaring that this is a task of type &lt;code class=&quot;language-text&quot;&gt;ProguardTask&lt;/code&gt; and that
it depends on &lt;code class=&quot;language-text&quot;&gt;jar&lt;/code&gt; task, meaning it has to run &lt;em&gt;after&lt;/em&gt; it. This &lt;code class=&quot;language-text&quot;&gt;jar&lt;/code&gt; task comes from either the kotlin or java plugin,
that you’re already using and it is responsible for packaging your application artifacts into a jar file. The reason
our &lt;code class=&quot;language-text&quot;&gt;obfuscateArtifact&lt;/code&gt; task needs to run after this one can be seen in the image above. Proguard takes a jar file as
the input, so we need this task to create the jar and then we’ll apply our task on top of it.&lt;/p&gt;
&lt;p&gt;Now let’s configure the input/output of our task:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; artifactName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;libraryArtifactId&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;libraryVersion&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.jar&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; obfuscatedFolder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;buildDir&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/obfuscated&quot;&lt;/span&gt;&lt;/span&gt;

injars &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;buildDir&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/libs/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;artifactName&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
outjars &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;obfuscatedFolder&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;artifactName&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;jar&lt;/code&gt; task will create, by default, the outputfile file in &lt;code class=&quot;language-text&quot;&gt;buildDir/libs&lt;/code&gt; folder. By default, its name follows the
pattern: &lt;code class=&quot;language-text&quot;&gt;artifactId-version.jar&lt;/code&gt;. The &lt;code class=&quot;language-text&quot;&gt;artifactId&lt;/code&gt; is defined in the publishing task that we’ll see later on. It’s
useful to have those values as variables declared in a &lt;code class=&quot;language-text&quot;&gt;gradle.properties&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Next properties are for handling deobfuscation. These are meant to be used when you want to understand your stacktraces.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;printseeds &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;obfuscatedFolder&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/seeds.txt&quot;&lt;/span&gt;&lt;/span&gt;
printmapping &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;obfuscatedFolder&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/mapping.txt&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Handling dependencies&lt;/h3&gt;
&lt;p&gt;After that, we have to make proguard understand our project’s dependencies. To do that we need to provide it with java’s
standard library and ours runtime, and compile-time dependencies. A quick explanation of the last two:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code class=&quot;language-text&quot;&gt;compile-time&lt;/code&gt; classpath contains the classes that you’ve added in order to compile your code. This is the
equivalent of the classpath passed to &lt;code class=&quot;language-text&quot;&gt;javac&lt;/code&gt; (or any other java compiler).&lt;/li&gt;
&lt;li&gt;The &lt;code class=&quot;language-text&quot;&gt;runtime:&lt;/code&gt; classpath contains the classes that are used when your application is running. That’s the classpath
passed to the &lt;code class=&quot;language-text&quot;&gt;java&lt;/code&gt; executable. This is all the code your library is using when it’s running.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In proguard we can use the &lt;code class=&quot;language-text&quot;&gt;libraryjars&lt;/code&gt; property to provide these dependencies:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;libraryjars &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;java.home&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/lib/rt.jar&quot;&lt;/span&gt;&lt;/span&gt;
libraryjars configurations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runtime
libraryjars sourceSets&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;compileClasspath&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first line has a path pointing to a &lt;code class=&quot;language-text&quot;&gt;rt.jar&lt;/code&gt;. This stands for &lt;code class=&quot;language-text&quot;&gt;runtime JAR&lt;/code&gt; and it has all the classes from the Core
Java API. This is the path you use when you’re targeting java 8 and below. The other two lines handles runtime and
compile-time dependencies that we get by using gradle’s dsl.&lt;/p&gt;
&lt;h3&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;The last piece of this puzzle is proguard’s configuration file. I prefer doing that in a separated file. If you’re using
intelij you can get nice syntax highlight for this file, which in my book is always a nice to have.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;configuration &lt;span class=&quot;token function&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;projectDir&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/gradle/configuration.pro&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The configuration you’re going to use is up to you, even if you don’t add anything in the config file, proguard will
handle code obfuscation by default. We’ll come back to this file again.&lt;/p&gt;
&lt;p&gt;Ok, now if you want to obfuscate your code you just need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run &lt;code class=&quot;language-text&quot;&gt;jar&lt;/code&gt; task&lt;/li&gt;
&lt;li&gt;run &lt;code class=&quot;language-text&quot;&gt;obfuscateArtifact&lt;/code&gt; task&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Problem solved! Or maybe not.&lt;/p&gt;
&lt;h1&gt;Automating the publication pipeline&lt;/h1&gt;
&lt;p&gt;Assuming you get the &lt;code class=&quot;language-text&quot;&gt;artifactId&lt;/code&gt; right, what we did until now may actually work for youu. The obfuscate task will
trigger a build and then obfuscate the jar. But that’s not ideal. We don’t want to manually run this specific task every
time. To me, the obfuscation step should be just a part of our library’s publication pipeline now. We shouldn’t have to
remember about it anymore. In order to fix this we need to take a look at this so called, publication pipeline.&lt;/p&gt;
&lt;p&gt;If you already have one, it may be just a simple &lt;code class=&quot;language-text&quot;&gt;publishing&lt;/code&gt; task, something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;group &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.myLibrary.group.&quot;&lt;/span&gt;&lt;/span&gt;
version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;myLibraryVersion&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;

publishing &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    publications &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MavenPublication&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            artifactId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;myLibraryArtifactId&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
            from components&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;java&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;My Library&apos;s name&quot;&lt;/span&gt;&lt;/span&gt;
            description &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;My Library&apos;s description&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The key here is the &lt;code class=&quot;language-text&quot;&gt;from components[&quot;java&quot;]&lt;/code&gt; declaration. Quoting from gradle’s docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Components are defined by plugins and provide a simple way to define a publication for publishing. They comprise one
or more artifacts as well as the appropriate metadata. For example, &lt;strong&gt;the java component consists of the production JAR
— produced by the jar task — and its dependency information&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, this is how our publication task gets the project’s artifacts and dependencies. Therefore, if we want to publish an
obfuscated library we need to remove that line and provide our publication with the output produced
by &lt;code class=&quot;language-text&quot;&gt;obfuscateArtifact&lt;/code&gt; task.&lt;/p&gt;
&lt;p&gt;So, how can we do that?
As we saw in the documentation the &lt;code class=&quot;language-text&quot;&gt;components[&quot;java&quot;]&lt;/code&gt; is automatically defined by the java/kotlin plugin and includes
all classes from the default &lt;code class=&quot;language-text&quot;&gt;sourceSets&lt;/code&gt;. If we want to define our own artifacts that we want to be published, we can
use the &lt;code class=&quot;language-text&quot;&gt;artifact&lt;/code&gt; property.
This is how you do it, inside the &lt;code class=&quot;language-text&quot;&gt;create&lt;/code&gt; block, add:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;&lt;span class=&quot;token function&quot;&gt;artifact&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;buildDir&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/obfuscated/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;libraryArtifactId&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.jar&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    builtBy obfuscateArtifact
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, let’s read this backwards:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;builtBy&lt;/code&gt; tells which task is going to build the artifact&lt;/li&gt;
&lt;li&gt;the argument to the &lt;code class=&quot;language-text&quot;&gt;artifact&lt;/code&gt; function is the path where this task is going to output it. This path was defined
earlier by the &lt;code class=&quot;language-text&quot;&gt;outputJar&lt;/code&gt; property, in our &lt;code class=&quot;language-text&quot;&gt;obfuscateArtifact&lt;/code&gt; task.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Almost there! Now, we need to provide the dependency information. This comes automatically when using
the &lt;code class=&quot;language-text&quot;&gt;components[&quot;java&quot;&lt;/code&gt;, but here we need to manually provide it. Still in the &lt;code class=&quot;language-text&quot;&gt;create&lt;/code&gt; block, add:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;groovy&quot;&gt;&lt;pre class=&quot;language-groovy&quot;&gt;&lt;code class=&quot;language-groovy&quot;&gt;pom&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withXml &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; dependencies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;asNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    configurations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;implementation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;allDependencies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;each &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; dep &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; depNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dependencies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dependency&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        depNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;groupId&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dep&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;group&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        depNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;artifactId&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dep&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        depNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dep&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;version&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that’s about it!
Now, we can forget about obfuscation. We just run the publish task and get our obfuscated code published seamlessly.&lt;/p&gt;
&lt;h2&gt;Naming conflicts&lt;/h2&gt;
&lt;p&gt;If you’re using this library in another codebase that will also obfuscate its code you might get some naming conflicts
due to proguard using the same name for classes/methods in that projects own obfuscation process. To fix this you can
provide proguard with a custom dictionary.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;txt&quot;&gt;&lt;pre class=&quot;language-txt&quot;&gt;&lt;code class=&quot;language-txt&quot;&gt;//class-dictionary.txt
Lorem
ipsum
dolor
sit
amet
consectetur
adipiscing
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You’ll then need tell proguard about it in the &lt;code class=&quot;language-text&quot;&gt;configuration.pro&lt;/code&gt; file, by adding the line.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;pro&quot;&gt;&lt;pre class=&quot;language-pro&quot;&gt;&lt;code class=&quot;language-pro&quot;&gt;-classobfuscationdictionary class-dictionary.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, proguard will use the words in that file to name our classes during the obfuscation step, avoiding naming
conflicts.&lt;/p&gt;
&lt;p&gt;The last thing I like to do is to create a different &lt;code class=&quot;language-text&quot;&gt;artifactId&lt;/code&gt; for the unobfuscated version of the library and to
publish both versions. You can see the complete script in
this &lt;a href=&quot;https://gist.github.com/iurysza/8b1dfb998bb9054cd708043a55f8e227&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;gist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This diagram shows our end product.
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/628lwza2a5nacpjkur1l.png&quot; alt=&quot;diagram&quot;&gt;&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.guardsquare.com/en/products/proguard&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Proguard home page&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Continuous Kotlin Static Analysis]]></title><description><![CDATA[What is it anyway? Static analysis is a way to find errors and other issues in a source code without actually executing it (hence, static…]]></description><link>https://iurysouza.dev/continuous-kotlin-static-analysis/</link><guid isPermaLink="false">https://iurysouza.dev/continuous-kotlin-static-analysis/</guid><pubDate>Sun, 20 Dec 2020 23:46:37 GMT</pubDate><content:encoded>&lt;h1&gt;What is it anyway?&lt;/h1&gt;
&lt;p&gt;Static analysis is a way to find errors and other issues in a source code without actually executing it (hence, static).
The process provides an understanding of the code structure and can help ensure that the code adheres to industry
standards and capture issues early.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In sum, it’s a glorified spellchecker.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is not a new idea, the first one of these tools is &lt;a href=&quot;https://www.wikiwand.com/en/Lint_(software)&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;lint&lt;/a&gt; which dates
back from the late 70’s and is the most common kind of static analysis tool. Most languages have a &lt;em&gt;linter&lt;/em&gt; nowadays.
The automation provided by these tools is fundamental to improve code quality, as it greatly increase code reviewing
speed while also being less prone to human error.&lt;/p&gt;
&lt;h4&gt;Let machines do what they’re best at!&lt;/h4&gt;
&lt;img width=&quot;100%&quot; style=&quot;width:100%&quot; src=&quot;https://thumbs.gfycat.com/AchingOptimisticAlabamamapturtle-size_restricted.gif&quot;&gt;
&lt;h3&gt;What can it do?&lt;/h3&gt;
&lt;p&gt;Static analysis can be broken into formal, cosmetic, design properties, error checking and predictive categories.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Formal&lt;/strong&gt; checks if the code is correct;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cosmetic&lt;/strong&gt; checks if the code syncs up with style standards;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Design properties&lt;/strong&gt; check the level of complexities;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error checking&lt;/strong&gt; looks for code violations;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Predictive&lt;/strong&gt; guesses how code will behave when run.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;But who needs this anyway?&lt;/h1&gt;
&lt;p&gt;In short, everyone does. Quoting John Carmack, lead developer of Wolfenstein, Doom, Quake and their sequels:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The first step is fully admitting that the code you write is riddled with errors. That is a bitter pill to swallow for
a lot of people, but without it, most suggestions for change will be viewed with irritation or outright hostility. You
have to want criticism of your code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Automation is necessary. It is common to take a sort of smug satisfaction in reports of colossal failures of automatic
systems, but for every failure of automation, the failures of humans are legion. Exhortations to “write better code”
plans for more code reviews, pair programming, and so on just don’t cut it, especially in an environment with dozens of
programmers under a lot of time pressure. The value in catching even the small subset of errors that are tractable to
static analysis every single time is huge.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;First Quality Gate&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot; style=&quot;&quot;&gt;
    &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1024px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/33fc77989113ebf52685cdab741f4668/f7007/aoe-quality-gates.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAAUABQDASIAAhEBAxEB/8QAGQABAAMBAQAAAAAAAAAAAAAAAAQFBgMH/8QAGAEAAgMAAAAAAAAAAAAAAAAABAUBAgf/2gAMAwEAAhADEAAAAcNb2/jmJE750CTA1ItWyxUn/8QAGhAAAwEBAQEAAAAAAAAAAAAAAwQFAgESBv/aAAgBAQABBQLVUE9DlYFmWCFGENxEbi73zDXDDe8ZRqNtqZz6PvnM6//EACURAQABAwIEBwAAAAAAAAAAAAECAAMRElETISJBBRQxYXGBkf/aAAgBAwEBPwG3f4c48KM7LJGWiRDfULHGYSHnbYImk6gqR4gyU8wi5ElIH3AiAPbAGKFZmVeqJz2fU++9YNj8+K//xAAjEQADAAECBQUAAAAAAAAAAAABAgMEERIABRQhMhATJDNR/9oACAECAQE/AcKnJM1lXJR+X5dM0ZPULT48lVGPswfQmCPUg7KHbPz6gbUnxTF5uaOQ+Vcb20smS9Er3+xKLTR1fyVvzhwBagAAAowAHYaajt6f/8QAJxAAAgICAQMDBAMAAAAAAAAAAQIDEQQSMQATIQUUQSIjUmFCcYH/2gAIAQEABj8CkkzMR8HLhh7kSTRdxJlQpuuLP3JYHl1bVk27iyeAprqeB5UGPnJ7SWBMj22UY5Sm5XJXsz4utHdkrZdojttR7cHpOPOq/S0s6++neQABjNkZZad5OL2oDxqqrQ6kxstdoZV4ZnO4/EIgB+bteDTHrHm9PmE2NisXX0t19s/FuIs0n7gk50yNVviXigJsHNxpeZEx8Q5MJb80mikcNfB2OwKkEVXQklZdrKfSCo+PNBqs35/j+q67FsFPiwx35B5+K/rploHU1ZAs/tqAF/4Ov//EABsQAQEBAQEBAQEAAAAAAAAAAAERIQAxUWFB/9oACAEBAAE/Ifpk2aSgSThiee2sfdHBLPCIR0SXIKU5SAghRIQYN0YkNAhriAh0bPvSLQJbKop4OK7HQ7xg8AhOANcs9S4Nf1jkBeTjhsI0ZEwaAvA/Dd7Xv/3/AEad1KfL3//aAAwDAQACAAMAAAAQZ899/8QAGhEBAAIDAQAAAAAAAAAAAAAAAQAhETFRYf/aAAgBAwEBPxAyL0pXnXzHNAY7FXUB3UStqQBoFk6CALoLFoADSUxz+3HfM//EABwRAQABBAMAAAAAAAAAAAAAAAERACExQZHh8P/aAAgBAgEBPxBKC62cqNCGsGvuaMCaEGEUgyxQRAHSYACIAABoLGqxi3ujiv/EABgQAQEBAQEAAAAAAAAAAAAAAAERACEx/9oACAEBAAE/EEfTarB2ShFV9nyIOy1oweGwTDrDAU8jk2/qQ2MmpiKcyJhdNb3HXKSAUPSOYuy8+JBudGtRveLhkuEc2ChIbQCwxFvoiQyEAhF4AOXhUGOegXov/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/33fc77989113ebf52685cdab741f4668/577d7/aoe-quality-gates.webp 300w,
/static/33fc77989113ebf52685cdab741f4668/93ccb/aoe-quality-gates.webp 600w,
/static/33fc77989113ebf52685cdab741f4668/d757a/aoe-quality-gates.webp 1024w&quot;
              sizes=&quot;(max-width: 1024px) 100vw, 1024px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/33fc77989113ebf52685cdab741f4668/622f1/aoe-quality-gates.jpg 300w,
/static/33fc77989113ebf52685cdab741f4668/58041/aoe-quality-gates.jpg 600w,
/static/33fc77989113ebf52685cdab741f4668/f7007/aoe-quality-gates.jpg 1024w&quot;
            sizes=&quot;(max-width: 1024px) 100vw, 1024px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/33fc77989113ebf52685cdab741f4668/f7007/aoe-quality-gates.jpg&quot;
            alt=&quot;Quality Gate - It&amp;#39;s something&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;&lt;p&gt;Quality Gate - It’s something&lt;/p&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;Static code analysis is a common DevOps practice and makes for a great first quality gate, since it provides early
feedback for developers making it easier to fix issues.
So, if you’ve reached this far, I’m hoping that I was able to convince you, but how can we get static analysis added to
your project?&lt;/p&gt;
&lt;h1&gt;Kotlin static analysis landscape&lt;/h1&gt;
&lt;p&gt;So, in the Kotlin community two tools usually come up when talking about static
analysis: &lt;a href=&quot;https://github.com/pinterest/ktlint&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ktlint&lt;/a&gt; and &lt;a href=&quot;https://github.com/detekt/detekt&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;detekt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ktlint&lt;/code&gt; prides itself for taking a &lt;em&gt;no-configuration&lt;/em&gt; approach to linting. The idea is that it will always reflect
kotlin’s &lt;a href=&quot;https://kotlinlang.org/docs/reference/coding-conventions.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;official code style&lt;/a&gt; so the idea here is to be
a cosmetic static analysis tool.
&lt;code class=&quot;language-text&quot;&gt;Detekt&lt;/code&gt; on the other hand provides many other features like complexity reports, code smell analysis while also being
extensively configurable.&lt;/p&gt;
&lt;h1&gt;Basic integration pitfalls&lt;/h1&gt;
&lt;p&gt;Both tools provide many ways to integrate in your project. You can add a plain gradle task, a gradle plugin or install
the command line interface. The recommended route is via a gradle plugin.
After integrating these tools in your project you can use tasks like &lt;code class=&quot;language-text&quot;&gt;detektCheck&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;ktlintCheck&lt;/code&gt; to run over all
your project and report the issues. And that’s it, you’re done!&lt;/p&gt;
&lt;h5&gt;Yes, but also no.&lt;/h5&gt;
&lt;p&gt;The problem with the basic integration is that in real life things tend to be a little different.
If you’re adding static analysis to a greenfield project, it’s usually fine to run those tasks over the whole project,
but if that is not your case, you’ll probably want to avoid auto-formatting your whole project or getting 5k+ warnings
every time you run one of those tasks. Warnings just turn into noise.
You could use a &lt;em&gt;baseline&lt;/em&gt;, which is a configuration that helps incremental adoption on a project, but you’re still
going to stumble in some issues related to running these over your whole codebase.&lt;/p&gt;
&lt;h2&gt;A better solution&lt;/h2&gt;
&lt;p&gt;If you stop and think about it to be able to get the benefits I’ve mentioned in the beginning of this post, ideally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You want these tasks to run fast, so you can run them often.&lt;/li&gt;
&lt;li&gt;You want to incrementally adopt this in your codebase&lt;/li&gt;
&lt;li&gt;And last, but certainly not least: you don’t want to keep forgetting to run these tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Configuring both tools is really similar, so I’ll highlight some points that happen in both scripts. You can get
detekt’s &lt;a href=&quot;https://gist.github.com/iurysza/005c08ff7f1892097c2823b1786ac087&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, the magic here is to get the files that were changed in our commit&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There’s git command that does just that:
&lt;code class=&quot;language-text&quot;&gt;$ git diff --name-only origin/main --relative &lt;/code&gt; In line 38 we call it by using groovy’s api. This will output all the
change files names.&lt;/li&gt;
&lt;li&gt;In line 44 we process the output so that we can pass the filename list to ktlint/detekt.&lt;/li&gt;
&lt;li&gt;From lines 13 to 20 we decide whether we need to run static analysis or just ignore all files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/iurysza/dada9047da83d101a6c4b63ed8407cb3&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ktlint.gradle-gist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And that’s about it! The rest of the code (from both scripts) just handles the report output location.&lt;/p&gt;
&lt;h4&gt;Good! Almost there now!&lt;/h4&gt;
&lt;p&gt;We just need a way to make this a part of our workflow. This way we won’t forget to run these tasks. And yeah, we can
use a &lt;code class=&quot;language-text&quot;&gt;git-hook&lt;/code&gt; like the &lt;code class=&quot;language-text&quot;&gt;pre-commit&lt;/code&gt; hook to do just that.&lt;/p&gt;
&lt;p&gt;If you’re not familiar with git-hooks, you can check them &lt;a href=&quot;https://www.atlassian.com/git/tutorials/git-hooks&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;, but
if you want to keep reading, just think about them as a piece of code that is going to be called when you do something
in git.&lt;/p&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;pre-commit&lt;/code&gt; hook will run when you try to commit something. It runs before git actually creates the commit, so the
idea is to only allow a new commit when our static analysis passes successfully, effectively creating our first quality
gate. Woot woot!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/iurysza/fc6d0b735089c573aa5b0ce8797ef548#file-pre-commit-hook&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;precommit-hook-gist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media.giphy.com/media/IwAZ6dvvvaTtdI8SD5/giphy.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;That’s it! This makes sure that every piece of code you commit has to follow the minimum quality standards that you and
your team agreed upon.
This is not yet a &lt;em&gt;final solution&lt;/em&gt; as people can bypass those checks by running &lt;code class=&quot;language-text&quot;&gt;git commit --no-verify&lt;/code&gt;, but it’s a
solid first step towards a better codebase.&lt;/p&gt;
&lt;p&gt;Besides, if you think about it, these scripts can be easily integrated to a CI environment. What if it could send those
warnings as inline comments in your pull request? Stay tuned for more =)&lt;/p&gt;
&lt;h4&gt;References&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gamasutra.com/view/news/128836/InDepth_Static_Code_Analysis.php&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Carmac’s blog&lt;/a&gt;
&lt;a href=&quot;https://searchsoftwarequality.techtarget.com/definition/static-analysis-static-code-analysis&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Static analysis definition&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The First Way]]></title><description><![CDATA[The First Way of DevOps: Automating our Value Stream Have you ever heard the phrase “it works on my machine”? It’s one of the biggest pain…]]></description><link>https://iurysouza.dev/devops-the-first-way/</link><guid isPermaLink="false">https://iurysouza.dev/devops-the-first-way/</guid><pubDate>Thu, 17 Dec 2020 23:46:37 GMT</pubDate><content:encoded>&lt;h1&gt;The First Way of DevOps: Automating our Value Stream&lt;/h1&gt;
&lt;p&gt;Have you ever heard the phrase “it works on my machine”? It’s one of the biggest pain points for any software
development team. But what if I told you that there’s a way to solve it? That’s where the First Way of DevOps comes in.&lt;/p&gt;
&lt;p&gt;The idea behind the First Way is simple: by identifying, measuring, and improving the flow of work through our value
stream, we can optimize and refine the ability to deliver value to our customers.&lt;/p&gt;
&lt;h2&gt;Let’s start with pipelines&lt;/h2&gt;
&lt;p&gt;You’re probably already using a build system to automate some parts of our value stream, but we can do better. Quoting
from the Devops Handbook:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In order to create fast and reliable flow from Dev to Ops, we must ensure that we always use &lt;strong&gt;production-like
environments&lt;/strong&gt; at every stage of the value stream. Furthermore, these environments must be created in an &lt;strong&gt;automated
manner&lt;/strong&gt;, ideally on demand from &lt;strong&gt;scripts&lt;/strong&gt; and configuration information &lt;strong&gt;stored&lt;/strong&gt; in &lt;strong&gt;version control&lt;/strong&gt;, and
entirely self-serviced, without any manual work required from Operations. Our goal is to ensure that we can **
re-create** the entire production environment based on what’s in version control.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The authors go on to say that we should enable on demand creation of dev, test, and production environments so
developers can run production-like environments anywhere, even on their own computers, created on demand and
self-serviced.&lt;/p&gt;
&lt;p&gt;This way, developers can run and test their code in a production-like environment as part of their daily work,
providing early and constant feedback on code quality.&lt;/p&gt;
&lt;p&gt;We’ve all been there before:
&lt;img src=&quot;https://4.bp.blogspot.com/_CywCAU4HORs/S-HYtX4vOkI/AAAAAAAAAHw/Hzi5PYZOkrg/s1600/ItWorksOnMyMachine.jpg&quot; alt=&quot;it works on my machine&quot;&gt;&lt;/p&gt;
&lt;p&gt;The “It works on my machine!” issue comes from the fact that in any complex system there’s just too many variables to
manage. If you google &lt;em&gt;The Matrix of Hell&lt;/em&gt; you’ll might see something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.speakerdeck.com/presentations/c5ce6ce091ee01319fc1623dfa5ee5e7/slide_6.jpg&quot; alt=&quot;Matrix from hell&quot;&gt;&lt;/p&gt;
&lt;p&gt;Different kind of applications will have different rows and columns in this table, but you get the idea: - Multiple
environments with multiple third party dependencies, create a combinatorial explosion that no sane human being can
manage.&lt;/p&gt;
&lt;p&gt;Most companies will have a test and production environments, maybe you even have a dev environment where you can safely
run your app with the hottest new features. But as the project evolves, non-production environments tend to degrade.
It’s hard for them to keep up with prod because of lack of automation or the discipline of constantly updating it. After
a while the dev environment will have so many small things that work differently that it becomes hard to trust it. Some
months down the line and people will stop believing anything that happens in the dev environment and only newcomers,
still unaware of these issues, will be using it at all.&lt;/p&gt;
&lt;h1&gt;How to fix that?&lt;/h1&gt;
&lt;p&gt;The issue is that if these environments were manually created, you can’t expect people to maintain them equally.
By automating their creation we enable anyone to quickly spin up a new environment on demand, enabling every developer
to have their own local environment when they need one.
The automation scripts will make sure that the correct configuration is loaded on each environment, avoiding hundreds or
thousands of hours of unneeded headaches over a misconfiguration due to human error.&lt;/p&gt;
&lt;p&gt;This way we’re effectively giving the development team access to production-like environments by treating your
infrastructure and configuration as code, making immutable infrastructure, ensuring where we operate, develop, and test
is comparable and a reliable indicator of system operation for customers — such that new value contributed by developers
can easily and predictably be deployed to customers.&lt;/p&gt;
&lt;h1&gt;It’s hard, but it pays in the long run!&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://imgs.xkcd.com/comics/the_general_problem.png&quot; alt=&quot;xkcd&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is not a simple issue to solve. And you should be mindful about when and where to start the process.&lt;/p&gt;
&lt;p&gt;In my experience, small startups or software studios tend to overlook this, because it’s too much to worry about this
stuff at their product stage.&lt;/p&gt;
&lt;p&gt;If your project doesn’t have to scale, fine. You can get away from following these ideas in smaller projects, but the
engineering team has to stay vigilant for when they should stop and implement a solution.&lt;/p&gt;
&lt;p&gt;If you get big enough the cost of not having on-demand, dev/prod environment parity will also scale, even faster than
your product. And hiring more people will only make the cost increase.
Hundreds or thousands of development hours go down the drain because of this issue. Bugs that are impossible to
reproduce make debugging even harder, fear of changing “buggy” parts of the application causes code quality to suffer
and everybody suffers.&lt;/p&gt;
&lt;p&gt;Every team and organization is different and deciding when to start with this approach has to made in case by case
scenario, but I’d argue that it’s certainly cost-effective to gradually implement this idea in the early stages of a
project.&lt;/p&gt;
&lt;h4&gt;Create our single source of truth for the entire system&lt;/h4&gt;
&lt;p&gt;To ensure that we can restore production service repeatedly and predictably (and, ideally, quickly) even when
catastrophic events occur, we must check in all the assets needed to recreate the system state.&lt;/p&gt;
&lt;p&gt;That includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All application code and dependencies (e.g., libraries, static content, etc.)&lt;/li&gt;
&lt;li&gt;Any script used to create database schemas, application reference data, etc.&lt;/li&gt;
&lt;li&gt;All the environment creation tools and artifacts&lt;/li&gt;
&lt;li&gt;Any file used to create containers (e.g., Docker or composition files)&lt;/li&gt;
&lt;li&gt;All supporting automated tests and any manual test scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ideally this should happen using immutable infrastructure where no manual changes occur - make it easier to destroy
and rebuild than to configure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Encoding knowledge&lt;/strong&gt; and &lt;strong&gt;sharing&lt;/strong&gt; it through a repository is one of the powerful mechanism for propagating
knowledge.
Particularly when it includes every library (internal or external), and those each have an owner responsible for
ensuring it successfully passes all tests.&lt;/p&gt;
&lt;h1&gt;Wrapping up&lt;/h1&gt;
&lt;p&gt;We’ve seen how dev/prod parity begets automation. If you’re not yet embracing these concepts, well, you quite frankly
should, or at least be aware of the issues that are coming your way.
But fret not! We’ve all been there and those who came out alive have some very interesting ideas about how to solve it.
We just have to listen to them.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[A primer]]></title><description><![CDATA[Hey there, folks! Recently, I’ve been on a mission to level up my understanding of good CI/CD practices, and you know what kept popping
up…]]></description><link>https://iurysouza.dev/devops-primer/</link><guid isPermaLink="false">https://iurysouza.dev/devops-primer/</guid><pubDate>Wed, 16 Dec 2020 23:46:37 GMT</pubDate><content:encoded>&lt;p&gt;Hey there, folks!&lt;/p&gt;
&lt;p&gt;Recently, I’ve been on a mission to level up my understanding of good CI/CD practices, and you know what kept popping
up? &lt;code class=&quot;language-text&quot;&gt;DevOps&lt;/code&gt;. At first, I was like “cool, so those are the people who keep our infrastructure running and manage our
pipelines, right? But, man, was I mistaken!&lt;/p&gt;
&lt;p&gt;So, I took it upon myself to get a better understanding of this whole DevOps thing. I read a book, scoured the
interwebs, and even talked to some experts. And now, I want to share my newfound knowledge with you all!&lt;/p&gt;
&lt;p&gt;The book I picked up was “The DevOps Handbook” - it’s a real treasure trove of information, written by some of the
smartest folks in the DevOps community. And while I was at it, I also stumbled upon “The Twelve-Factor App” - a
methodology for developing software as a service (SaaS). It’s a great read, even if you’re not in the SaaS game.&lt;/p&gt;
&lt;p&gt;Anyway, let’s get to the good stuff! What is DevOps, you ask?&lt;/p&gt;
&lt;p&gt;Well, according to good ol’ Wikipedia:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;DevOps is a set of practices that combines software development and IT operations.
It aims to shorten the system’s development life cycle and provide continuous delivery with high software quality.
DevOps is complementary with Agile software development; several DevOps aspects came from Agile methodology.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In simpler terms, DevOps is like Agile, but at a macro level. When you think about all the Agile methodologies, best
practices, tools, etc., the ultimate goal is to deliver value to the customer. And that’s where the concept of a “value
stream” comes in.&lt;/p&gt;
&lt;p&gt;A value stream is the process of converting a business hypothesis into a technology-enabled service that delivers value
to the customer.&lt;/p&gt;
&lt;p&gt;And the DevOps Handbook breaks down DevOps practices into three ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The First Way&lt;/strong&gt;: fast flow of work from Development to Operations to the customer. To make this happen, work needs
to be visible, batch sizes and intervals need to be reduced, quality needs to be built in, and everything needs to be
optimized for global goals.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Second Way&lt;/strong&gt;: fast and constant flow of feedback from right to left. This means amplifying feedback to prevent
problems from happening again and detecting and recovering from problems faster.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Third Way&lt;/strong&gt;: creating a generative, high-trust culture that supports experimentation and risk-taking. This
leads to organizational learning, both from successes and failures, and enables the organization to learn faster than
the competition.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The book goes into more detail about each of these ways, with case studies from companies like Netflix and Amazon.&lt;/p&gt;
&lt;p&gt;But enough about the book - let’s &lt;a href=&quot;/devops-the-first-way&quot;&gt;dive in&lt;/a&gt; into the topics I found most interesting!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Type safe scripts]]></title><description><![CDATA[A kotlin script is a way to compile and run  code easily.
Scripts are a powerful tool when you want to automate any kind of work. The most…]]></description><link>https://iurysouza.dev/kotlin-scripting/</link><guid isPermaLink="false">https://iurysouza.dev/kotlin-scripting/</guid><pubDate>Sun, 26 Jul 2020 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A kotlin script is a way to &lt;strong&gt;compile&lt;/strong&gt; and &lt;strong&gt;run&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;Kotlin&lt;/code&gt; code easily.
Scripts are a powerful tool when you want to automate any kind of work. The most used languages for scripting tend to
be &lt;code class=&quot;language-text&quot;&gt;python&lt;/code&gt;
or &lt;code class=&quot;language-text&quot;&gt;bash&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;shell&lt;/code&gt; but, what if you could get the same task done with &lt;code class=&quot;language-text&quot;&gt;Kotlin&lt;/code&gt;?
But not just that. What if you had access to any java or kotlin library in your scripts?&lt;/p&gt;
&lt;p&gt;With &lt;code class=&quot;language-text&quot;&gt;Kscript&lt;/code&gt; you can easily do that! Besides it also offers a lot of features.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scripts caching: Running the same script will be way faster the second time.&lt;/li&gt;
&lt;li&gt;Maven dependencies&lt;/li&gt;
&lt;li&gt;IntelliJ support&lt;/li&gt;
&lt;li&gt;Bootstrap header&lt;/li&gt;
&lt;li&gt;Check the &lt;a href=&quot;https://github.com/holgerbrandl/kscript&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;kscript repo&lt;/a&gt; for the full feature set.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h3&gt;Trying it out&lt;/h3&gt;
&lt;p&gt;Install it with &lt;a href=&quot;https://sdkman.io&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;sdk man&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ sdk &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; kscript&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
Now let&apos;s create a `.kts` file and try importing some libraries to create an embedded server:
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;#&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;usr&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;bin&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;env kscript

&lt;span class=&quot;token annotation builtin&quot;&gt;@file:MavenRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bintray-ktor&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://dl.bintray.com/kotlin/ktor&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation builtin&quot;&gt;@file:DependsOnMaven&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;io.ktor:ktor-server-netty:1.2.6&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ktor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;application&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ktor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ktor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ktor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ktor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;engine&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ktor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;netty&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;starting server...&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;embeddedServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Netty&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; port &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  routing &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello World!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ContentType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Plain&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;wait &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, lets run it!&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ kscript kscript-server.kts&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Open your browser on &lt;code class=&quot;language-text&quot;&gt;localhost:8080/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Cool, right?&lt;/p&gt;
&lt;p&gt;But if you’re like me you probably do most of your work on the IDE. So, let’s try that now.&lt;/p&gt;
&lt;p&gt;If you have Intelij Idea on your machine you can type&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ kscript &lt;span class=&quot;token parameter variable&quot;&gt;--idea&lt;/span&gt; kscript-server.kts&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and it will generate a Gradle Kotlin project with all dependencies declared with the &lt;code class=&quot;language-text&quot;&gt;@file&lt;/code&gt; convention and add them to
a Gradle file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;#Protip&lt;/strong&gt;: If syntax highlighting is not working properly, just right click on the &lt;code class=&quot;language-text&quot;&gt;build.gradle&lt;/code&gt; file
select &lt;code class=&quot;language-text&quot;&gt;Import Gradle project&lt;/code&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[A Twitter-Telegram bot in Kotlin]]></title><description><![CDATA[Have you ever wondered how Twitter bots work?
Can we integrate them with Telegram?
Is it possible to do this only with Kotlin? Fret not…]]></description><link>https://iurysouza.dev/writing_a_twitter_bot_with_kotlin/</link><guid isPermaLink="false">https://iurysouza.dev/writing_a_twitter_bot_with_kotlin/</guid><pubDate>Thu, 09 Jul 2020 11:53:37 GMT</pubDate><content:encoded>&lt;p&gt;Have you ever wondered how &lt;strong&gt;Twitter bots&lt;/strong&gt; work?
Can we integrate them with &lt;strong&gt;Telegram&lt;/strong&gt;?
Is it possible to do this &lt;strong&gt;only&lt;/strong&gt; with &lt;strong&gt;Kotlin&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;Fret not! Follow me while we uncover these mysteries!&lt;/p&gt;
&lt;h3&gt;What we’ll be creating&lt;/h3&gt;
&lt;p&gt;The idea here is pretty straight forward:
A Twitter bot that downloads videos from tweets and sends them to me on Telegram.&lt;/p&gt;
&lt;p&gt;Whenever I reply to a post on Twitter that has a video, tagging the bot’s username, the bot will download the video and send it to me on telegram.
Simple enough, right? You might have even seen something similar in the wild.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/ov4jueb.png&quot; alt=&quot;Example of a similar bot in the wild&quot; title=&quot;@BaixaEssaPorra is a popular downloader bot in the brazilian twittersphere&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Getting a little more technical&lt;/h3&gt;
&lt;p&gt;Now that we know what we want, how can we achieve this?&lt;/p&gt;
&lt;p&gt;We’ll have an application running locally on our machines that will somehow &lt;strong&gt;listen to tweets that mention another user&lt;/strong&gt;.
After being notified of this event, we will need to &lt;strong&gt;fetch the video URL&lt;/strong&gt; from the tweet we’re replying to, &lt;strong&gt;download the video&lt;/strong&gt; and finally &lt;strong&gt;send it over to a telegram user&lt;/strong&gt; using the a bot.&lt;/p&gt;
&lt;p&gt;After reading the &lt;a href=&quot;https://developer.twitter.com/en/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Twitter developer docs&lt;/a&gt; I’ve found they offer 3 possibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-mentions_timeline&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;HTTP Polling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.twitter.com/en/docs/tutorials/consuming-streaming-data#consuming-the-stream&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;HTTP Streaming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.twitter.com/en/docs/accounts-and-users/subscribe-account-activity/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;WebHooks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(If you’re not familiar with these concepts you can check this &lt;a href=&quot;https://medium.com/@jeff.lee6268/getting-started-with-realtime-b5a8773deb1a&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;article&lt;/a&gt; to understand the basics.)&lt;/p&gt;
&lt;h3&gt;Choosing our tools&lt;/h3&gt;
&lt;p&gt;Now, since we’re planning to use Kotlin to develop this, the first thing I did before diving-in was a little research (as usual). It seems we have some library options to make this task a little smoother.&lt;/p&gt;
&lt;p&gt;Here’s what I found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There’s an unofficial Java library for the Twitter API called &lt;a href=&quot;http://twitter4j.org/en/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Twitter4j&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;There’s a Java HTTP client for consuming Twitter’s realtime streaming API called &lt;a href=&quot;https://github.com/twitter/hbc&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;HBC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;There’s a wrapper for the Telegram bot API written in Kotlin called &lt;a href=&quot;https://github.com/kotlin-telegram-bot/kotlin-telegram-bot&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;kotlin-telegram-bot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sweet! The job just got a whole lot easier now that we don’t need to build the HTTP clients for any of these.&lt;/p&gt;
&lt;p&gt;The only issue is that we don’t have any java/kotlin library to work with the &lt;code class=&quot;language-text&quot;&gt;WebHooks&lt;/code&gt; api, so we’ll be using the ones above.&lt;/p&gt;
&lt;h3&gt;Let’s code!&lt;/h3&gt;
&lt;p&gt;First of all we need to get the keys to play with these services.&lt;/p&gt;
&lt;p&gt;To get Twitter keys we’ll need to create a Twitter App, you can get yours &lt;a href=&quot;https://developer.twitter.com/en/account/get-started&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;
To get &lt;code class=&quot;language-text&quot;&gt;Telegram&lt;/code&gt;’s token is just as simple as talking to the &lt;a href=&quot;https://web.telegram.org/#/im?p=@BotFather&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;BotFather&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Hiding keys, tokens and secrets&lt;/h4&gt;
&lt;p&gt;First, we need a mechanism to load keys, secrets, userIds and tokens used in the project. It is a good practice to keep our keys out of version control.&lt;/p&gt;
&lt;p&gt;To do that, we create a json file with all the keys and put it in the &lt;code class=&quot;language-text&quot;&gt;resources&lt;/code&gt; folder.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;consumerKey&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;abc1234DEF1234GHI456&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;consumerSecret&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;abc1234DEF1234GHI456abc1234DEF1234GHI456&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;token&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0123456789-abc1234DEF1234GHI456&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tokenSecret&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;abc1234DEF1234GHI456abc1234DEF1234GHI456&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;telegramToken&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0123456789:abc1234DEF1234GHI456-abc1234DEF1234GHI456&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;twitterUserId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;012345678901234567890123456789&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;telegramUserId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;01234567890123456789&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, we can use &lt;a href=&quot;https://github.com/square/moshi&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Moshi&lt;/a&gt; to simplify deserialization and the &lt;code class=&quot;language-text&quot;&gt;AuthLoader&lt;/code&gt; class to wrap it up.&lt;/p&gt;
&lt;p&gt;Add this to your &lt;code class=&quot;language-text&quot;&gt;gradle&lt;/code&gt; file&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.squareup.moshi:moshi:1.9.3&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.squareup.moshi:moshi-kotlin:1.9.3&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;kapt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.squareup.moshi:moshi-kotlin-codegen:1.9.3&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; MoshiWrapper &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; instance&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Moshi &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Moshi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;KotlinJsonAdapterFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;inline&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;reified&lt;/span&gt; T &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Any&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fromJson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; KClass&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; json&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; T&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromJson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;json&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@JsonClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;generateAdapter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AuthData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; consumerKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; consumerSecret&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; token&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; tokenSecret&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; telegramToken&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; twitterUserId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; telegramUserId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; AuthLoader &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toOAuth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AuthData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; OAuth1 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; consumerSecret&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokenSecret&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; authData
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;OAuth1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; consumerSecret&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokenSecret&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getAuthDataFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AuthData &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; fileContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFileFromResources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;fileName&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; MoshiWrapper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromJson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            AuthData&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            fileContent
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!!&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFileFromResources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; resource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getResource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resource &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;file not found!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Filtering real time tweets&lt;/h4&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;docs&lt;/a&gt; tell us that we can filter tweets using some parameters like &lt;code class=&quot;language-text&quot;&gt;keywords&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;userIds&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In order to do that we can use the &lt;code class=&quot;language-text&quot;&gt;HBC-core&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;HBC-Twitter4j&lt;/code&gt; integration.&lt;/p&gt;
&lt;p&gt;So, we’ll be using a &lt;code class=&quot;language-text&quot;&gt;Twitter4jStatusClient&lt;/code&gt; to listen to filtered status updates. This class is a wrapper of HBC’s &lt;code class=&quot;language-text&quot;&gt;BasicClient&lt;/code&gt; . It is responsible for opening the connection with the Streaming API and to pass filter parameters. The Integration with &lt;code class=&quot;language-text&quot;&gt;Twitter4j&lt;/code&gt; removes the need to model and deserialize the json responses when receiving status updates.&lt;/p&gt;
&lt;p&gt;First we add the dependencies&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.twitter:hbc-core:2.2.0&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;com.twitter:hbc-twitter4j:2.2.0&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;org.twitter4j:twitter4j-core:4.0.7&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;org.slf4j:slf4j-log4j12:1.6.0&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//needed for twitter4j&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;io.github.kotlin-telegram-bot.kotlin-telegram-bot:telegram:5.0.0&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is the implementation I came up to simplify consuming this stream:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;FilteredStatusStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; authData&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AuthData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; filterParams&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; FilterParams&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; statusListener&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; Unit
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;companion&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; CAPACITY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1_000&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; THREAD_POOL_SIZE &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; statusClient&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Twitter4jStatusClient&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;followings&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; keywords&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filterParams

        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; filters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createStatusFilters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;followings&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; keywords&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; msgQueue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; LinkedBlockingQueue&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;String&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; CAPACITY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createBasicClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filters&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; msgQueue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; CAPACITY&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        statusClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createStatusClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; msgQueue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; THREAD_POOL_SIZE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        statusClient&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createStatusFilters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;followings&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; List&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Long&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; terms&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; List&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;String&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;StatusesFilterEndpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;followings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;followings&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;trackTerms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;terms&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createStatusClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        client&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BasicClient&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        msgQueue&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LinkedBlockingQueue&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;String&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        poolSize&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Twitter4jStatusClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        msgQueue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;DefaultStatusListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;statusListener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        Executors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newFixedThreadPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;poolSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createBasicClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        endpoint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StatusesFilterEndpoint&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        msgQueue&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LinkedBlockingQueue&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;String&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        eventQueueSize&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Int
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ClientBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;HttpHosts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Constants&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;STREAM_HOST&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AuthLoader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toOAuth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;endpoint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;StringDelimitedProcessor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;msgQueue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eventMessageQueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;LinkedBlockingQueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eventQueueSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It looks like a lot, but it is really just configuration code. We’re just hiding the complexity of setting up a Twitter steaming client.
Now we can listen to the tweets from the user with the &lt;code class=&quot;language-text&quot;&gt;twitterUserId&lt;/code&gt; whenever he mentions the tweeter handle &lt;code class=&quot;language-text&quot;&gt;@MandaProZap&lt;/code&gt; just by calling:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; authData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AuthLoader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAuthDataFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;auth_data.json&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; filterParams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;FilterParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;twitterUserId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@MandaProZap&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;FilteredStatusStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filterParams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; newStatus &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newStatus&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Awesome, right?!😁&lt;/p&gt;
&lt;p&gt;But… how do we get the video data?
First of all, just a quick recap: We want to get the video from the &lt;strong&gt;tweet&lt;/strong&gt; that we’re &lt;strong&gt;replying&lt;/strong&gt; to.&lt;/p&gt;
&lt;p&gt;This &lt;code class=&quot;language-text&quot;&gt;newStatus&lt;/code&gt; we’re getting from the stream is our &lt;strong&gt;own tweet&lt;/strong&gt;. So we still need to get the parent tweet and it’s video.&lt;/p&gt;
&lt;h4&gt;Getting media from the parent tweet&lt;/h4&gt;
&lt;p&gt;Inspecting the &lt;code class=&quot;language-text&quot;&gt;Status&lt;/code&gt; returned from the stream we can see it’s properties:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt; createdAt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; displayTextRangeStart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; displayTextRangeEnd &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;boolean&lt;/span&gt; isTruncated&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; inReplyToStatusId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;//This is the one we need&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can use &lt;code class=&quot;language-text&quot;&gt;Twitter4j&lt;/code&gt; to fetch the parent tweet.&lt;/p&gt;
&lt;p&gt;Again, we can simplify it’s usage with this class:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TwitterClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AuthData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; twitter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Twitter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TwitterFactory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instance

    &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        twitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setOAuthConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;consumerKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;consumerSecret&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; accessToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AccessToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tokenSecret&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        twitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;oAuthAccessToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; accessToken
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getStatusById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;statusId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Long&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Status&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; runCatching &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; twitter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;statusId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally we can get the video url like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; twitterClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TwitterClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
twitterClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStatusById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inReplyToStatusId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mediaEntities
            &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapNotNull&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;videoVariants&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;firstOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Almost there now! We just need to send the video to Telegram! But wait… if we send this url… we’ll be sending just an url… not an actual video. We can do better!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Downloading the video&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since we’re using the &lt;code class=&quot;language-text&quot;&gt;kotlin-telegram-bot&lt;/code&gt;, and it has transitive dependencies to &lt;a href=&quot;https://github.com/square/retrofit&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;retrofit&lt;/a&gt; it seems like a good idea to using it to download the video file.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; FileDownloader &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; service &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Retrofit
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;baseUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://localhost/&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;FileDownloadService&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;downloadAndWriteToFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; File&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Dispatchers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IO&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;downloadFileFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;writeToFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;writeToFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;responseBody&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ResponseBody&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; File&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; runCatching &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token expression&quot;&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;currentTimeMillis&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.mp4&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;createNewFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            Okio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Okio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sink&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;responseBody&lt;span class=&quot;token operator&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; FileDownloadService &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation builtin&quot;&gt;@GET&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;downloadFileFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token annotation builtin&quot;&gt;@Url&lt;/span&gt; fileUrl&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Call&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ResponseBody&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here we’re creating a &lt;code class=&quot;language-text&quot;&gt;FileDownloadService&lt;/code&gt; with &lt;code class=&quot;language-text&quot;&gt;Retrofit&lt;/code&gt; and use it to download the url.&lt;/p&gt;
&lt;p&gt;After that we write the data to a file using &lt;code class=&quot;language-text&quot;&gt;Okio&lt;/code&gt; (also comes bundled with retrofit).&lt;/p&gt;
&lt;p&gt;You can see that we’re using coroutines to switch threads while it still looks like synchronous code.&lt;/p&gt;
&lt;p&gt;This was achieved with a simple extension function on &lt;code class=&quot;language-text&quot;&gt;retrofit&lt;/code&gt;’s &lt;code class=&quot;language-text&quot;&gt;Call&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; Call&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; T &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; suspendCancellableCoroutine &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; continuation &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Callback&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;call&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Call&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Response&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isSuccessful &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    continuation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resumeWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    continuation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resumeWithException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onFailure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;call&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Call&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; t&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Throwable&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                continuation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resumeWithException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ok, now we’re downloading the video file. We just need to send it over to Telegram!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sending the video to Telegram&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the easiest part thanks to the amazing &lt;code class=&quot;language-text&quot;&gt;kotlin-telegram-bot&lt;/code&gt; library.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; telegramBot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; bot &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;telegramToken &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
telegramBot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendVideo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;telegramUserId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; videoFile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Putting it all together&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; authData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AuthLoader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAuthDataFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;auth_data.json&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; filterParams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;FilterParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;twitterUserId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@MandaProZap&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; twitterClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TwitterClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; telegramBot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; bot &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;telegramToken &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;FilteredStatusStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filterParams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; newStatus &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
        twitterClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStatusById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inReplyToStatusId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mediaEntities
            &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapNotNull&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;videoVariants&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;firstOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
                GlobalScope&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    FileDownloader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;downloadAndWriteToFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; videoFile &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
                        telegramBot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendVideo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;telegramUserId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; videoFile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; authData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AuthLoader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAuthDataFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;auth_data.json&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; filterParams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;FilterParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;twitterUserId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal singleline&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@MandaProZap&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; twitterClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TwitterClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; telegramBot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; bot &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;telegramToken &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;FilteredStatusStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filterParams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; newStatus &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
        twitterClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getStatusById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inReplyToStatusId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mediaEntities
            &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mapNotNull&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;videoVariants&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;firstOrNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
                GlobalScope&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    FileDownloader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;downloadAndWriteToFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; videoFile &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
                        telegramBot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendVideo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;authData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;telegramUserId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; videoFile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Summing it all up!&lt;/h3&gt;
&lt;p&gt;Yes, we can create a twitter-telegram bot with Kotlin while keeping it fairly simple.&lt;/p&gt;
&lt;p&gt;Taking advantage of these libraries make the experience a lot smoother and adding some wrappers on top of it helps to hide the complexity and enable us to create a declarative API to let us focus on what matters.&lt;/p&gt;
&lt;p&gt;The next step here is to implement this idea using the &lt;code class=&quot;language-text&quot;&gt;WebHook API&lt;/code&gt;, but this will have to be in a another post.&lt;/p&gt;
&lt;p&gt;I hope you learned something along the way, I certainly did!&lt;/p&gt;
&lt;p&gt;You can check the project on &lt;a href=&quot;https://github.com/iurysza/twitter-telegram-bot&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;github&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Setting up MockServer]]></title><description><![CDATA[Main goals: Simple and declarative testing environment setup. Runs everywhere Strategy Set up a replicable local server. Docker…]]></description><link>https://iurysouza.dev/setting-up-mockserver/</link><guid isPermaLink="false">https://iurysouza.dev/setting-up-mockserver/</guid><pubDate>Tue, 21 Apr 2020 07:03:47 GMT</pubDate><content:encoded>&lt;h2&gt;Main goals:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Simple and declarative testing environment setup.&lt;/li&gt;
&lt;li&gt;Runs everywhere&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Strategy&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Set up a replicable local server.&lt;/p&gt;
&lt;p&gt;Docker - &lt;em&gt;Containerized applications (actually write once, runs anywhere)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provide an expectations json where all the requests are mapped out.&lt;/p&gt;
&lt;p&gt;MockServer - Create local server with complex request/response expectations through a json resource or a REST API.&lt;/p&gt;
&lt;img src=&quot;https://miro.medium.com/max/1400/0*LOnXgsR6pNv7mHvu.png&quot; alt=&quot;MockServer&quot; style=&quot;zoom:33%;&quot;&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Making it happen&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.docker.com/get-docker/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Install Docker&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&quot;https://docs.docker.com/compose/install/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Docker Compose&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make sure your docker daemon is up and running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a folder for your MockServer setup.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; /path/to/mockserver_container/ &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; /path/to/mockserver_container&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the &lt;code class=&quot;language-text&quot;&gt;docker-compose.yml&lt;/code&gt; .&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This is where you’ll setup your container for MockServer.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; ./docker-compose.yml &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;nano&lt;/span&gt; ./docker-compose.yml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Paste this code snippet:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2.4&apos;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;mockServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mockserver/mockserver&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest
ports&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1080:1080
environment:
MOCKSERVER_PROPERTY_FILE: /config/mockserver.properties
MOCKSERVER_INITIALIZATION_JSON_PATH: /config/initializerJson.json
volumes:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;type: bind
source: .
target: /config&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;
This is saying:

- Create a service named `mockserver` using the docker image from `mockserver/mockserver:latest`.

- Assign the `host port` 1080 to the `container port` 1080.
- Set the `enviroment variables` to the provided paths.

- Create a `volume` binding from the `current directory` (this file&apos;s dir) to `/config` inside the container.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Setup the Expectation Initializer JSON.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create the &lt;code class=&quot;language-text&quot;&gt;initializerJson&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; ./initializerJson.json &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;nano&lt;/span&gt; ./initializerJson.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Paste this code snippet:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;httpRequest&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/api/v1/weather&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;queryStringParameters&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;10969&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;httpResponse&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{\&quot;wheather\&quot;: 20.5}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;statusCode&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Full documentation reference can be found &lt;a href=&quot;https://app.swaggerhub.com/apis/jamesdbloom/mock-server-openapi/5.9.x#/Expectation&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Running your container&lt;/h2&gt;
&lt;p&gt;Now that we’ve got the plumbing out of the way, it’s time to run it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Before running your container check if have any other containers in your environment:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This should print an empty table.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build your container:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inside the folder you created the &lt;code class=&quot;language-text&quot;&gt;.yml&lt;/code&gt; type:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker-compose&lt;/span&gt; up&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Your terminal should print something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Creating mockserver_mockServer_1 &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;
Attaching to mockserver_mockServer_1
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-Dfile.encoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;UTF-8 &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; /opt/mockserver/mockserver-netty-jar-with-dependencies.jar  &lt;span class=&quot;token parameter variable&quot;&gt;-server&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2020&lt;/span&gt;-04-11 &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;:52:30  org.mockserver.log.MockServerEventLog  INFO  creating expectation:
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;3f3962d2-9c03-4dce-95cd-2522962ceccb&quot;&lt;/span&gt;,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token string&quot;&gt;&quot;priority&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token string&quot;&gt;&quot;httpRequest&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token string&quot;&gt;&quot;method&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GET&quot;&lt;/span&gt;,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token string&quot;&gt;&quot;path&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/api/v1/weather&quot;&lt;/span&gt;,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token string&quot;&gt;&quot;queryStringParameters&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;         &lt;span class=&quot;token string&quot;&gt;&quot;code&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10969&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token string&quot;&gt;&quot;times&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token string&quot;&gt;&quot;unlimited&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token string&quot;&gt;&quot;timeToLive&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token string&quot;&gt;&quot;unlimited&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token string&quot;&gt;&quot;httpResponse&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token string&quot;&gt;&quot;statusCode&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       &lt;span class=&quot;token string&quot;&gt;&quot;body&quot;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{&lt;span class=&quot;token entity&quot; title=&quot;\&amp;quot;&quot;&gt;\&quot;&lt;/span&gt;wheather&lt;span class=&quot;token entity&quot; title=&quot;\&amp;quot;&quot;&gt;\&quot;&lt;/span&gt;: 20.5}&quot;&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2020&lt;/span&gt;-04-11 &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;:52:30  org.mockserver.cli.Main  INFO  logger level is INFO, change using:
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  - &lt;span class=&quot;token string&quot;&gt;&apos;ConfigurationProperties.logLevel(String level)&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; Java code,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  - &lt;span class=&quot;token string&quot;&gt;&apos;-logLevel&apos;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;command&lt;/span&gt; line argument,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  - &lt;span class=&quot;token string&quot;&gt;&apos;mockserver.logLevel&apos;&lt;/span&gt; JVM system property or,
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;  - &lt;span class=&quot;token string&quot;&gt;&apos;mockserver.logLevel&apos;&lt;/span&gt; property value &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;mockserver.properties&apos;&lt;/span&gt;
mockServer_1  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2020&lt;/span&gt;-04-11 &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;:52:30  org.mockserver.log.MockServerEventLog  INFO  started on port: &lt;span class=&quot;token number&quot;&gt;1080&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Sending requests&lt;/h2&gt;
&lt;p&gt;Open a new terminal window and enter:&lt;/p&gt;
&lt;p&gt;To see info about your available containers enter again:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can now see some info about your running container.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; localhost:1080/api/v1/weather&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;?code&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10969&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You should see the response:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{&quot;wheather&quot;: 20.5}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And thats it!&lt;/p&gt;
&lt;p&gt;You’ve got yourself a local server where you can mock simple and complex requests and responses.&lt;/p&gt;
&lt;h3&gt;Useful links:&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://app.swaggerhub.com/apis/jamesdbloom/mock-server-openapi/5.9.x&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;REST API Swagger docs&lt;/a&gt;&lt;/p&gt;</content:encoded></item></channel></rss>