<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jameson</title>
    <description>The latest articles on DEV Community by Jameson (@jameson).</description>
    <link>https://dev.to/jameson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F53825%2F9b03e5ca-6ac6-46d9-ad5c-ef81af09f495.jpeg</url>
      <title>DEV Community: Jameson</title>
      <link>https://dev.to/jameson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jameson"/>
    <language>en</language>
    <item>
      <title>Pre-push Hooks</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Wed, 10 Apr 2024 21:56:27 +0000</pubDate>
      <link>https://dev.to/jameson/pre-push-hooks-42g5</link>
      <guid>https://dev.to/jameson/pre-push-hooks-42g5</guid>
      <description>&lt;h1&gt;
  
  
  What is a Hook?
&lt;/h1&gt;

&lt;p&gt;Git has a few different extension points where you can execute arbitrary scripts. Two of the most well-known are the pre-commit and pre-push hooks. As discussed below, these hooks allow you to perform additional validation before completing a commit or push.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why Not Just CI?
&lt;/h1&gt;

&lt;p&gt;As a repository grows, you onboard more linting tools, test suites, and other validations. The goal is to catch regressions and bad practices as early as possible. CI/CD (e.g., &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;) provides a means to ensure that these validations are run on every PR and every commit before being merged, as an invariant.&lt;/p&gt;

&lt;p&gt;However, CI/CD jobs can grow to be quite slow - hours, in some cases. For certain tasks that are quick to run, you'd probably prefer to run them on your local development machine before raising a PR.&lt;/p&gt;

&lt;p&gt;For example, checking if the locally-changed code is well-formatted is one task that can be done fairly quickly, and makes sense to do before raising a PR.&lt;/p&gt;

&lt;p&gt;On the other hand, running a full test suite may be best handled by the CI as a final check. Most changes aren't at risk of breaking unrelated tests, and the developer would like to work on other things while CI works in parallel.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pre-commit vs. Pre-push
&lt;/h1&gt;

&lt;p&gt;As with most things in Engineering, there is a trade-off between instrumenting your validations in a pre-commit vs. a pre-push hook.&lt;/p&gt;

&lt;p&gt;The pre-commit hook will run much more often, and can potentially enable you to keep each commit in a valid state. However, if you need to invoke the project's build system to complete validation, it may start to become a nuisance and ultimately slow you down. Worse, if you're making surgical, atomic commits, the pre-commit hook might even mess up your tree.&lt;/p&gt;

&lt;p&gt;Pre-push hooks don't have most of those problems and are the final opportunity to check code before it goes up for PR. One downside: you may end up with an additional "fix formatting"-type commit in your log this way. But, if you have GitHub configured to squash commits on merge, it doesn't matter much. Since pre-push hooks are run much less frequently, it also makes sense to perform some slightly longer validations in this hook.&lt;/p&gt;

&lt;p&gt;In a pre-push hook, you have already established a lineage of commits that will be sent for PR. So, it is also a safer time to apply &lt;em&gt;edits&lt;/em&gt; to the codebase, if you want to. For example, if the code in the push ref has bad formatting, we might like to format the code instead of just simply checking it. In a pre-commit hook, that might be disruptive to in-flight coding.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to build a pre-push system
&lt;/h1&gt;

&lt;p&gt;Various tools do provide their own integrations to Git hooks. For example, The Gradle ktlint Plugin &lt;a href="https://github.com/JLLeitschuh/ktlint-gradle?tab=readme-ov-file#additional-helper-tasks" rel="noopener noreferrer"&gt;offers a few tasks&lt;/a&gt; to register check/format tasks as a Git hook. But, as soon as you want to run multiple tasks in your hook, you'll probably wish you had better machinery.&lt;/p&gt;

&lt;p&gt;So, let's state a few of our up-front goals. We want to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an extensible script that will allow us to run multiple validation scripts during push;&lt;/li&gt;
&lt;li&gt;Provide clear and actionable feedback to the user about what is happening with the push - did it succeed or not, and why not?;&lt;/li&gt;
&lt;li&gt;Provide a simple way for developers on our team to install the pre-push scripts and get pertinent updates as improvements are added.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To solve the first problem, let's create a directory in our project called &lt;code&gt;./scripts/pre-push.d&lt;/code&gt; (inspired by the Linux/UNIX practice of splitting out config stubs in &lt;code&gt;.d&lt;/code&gt; directories.) We'll plan to have a &lt;code&gt;./scripts/pre-push&lt;/code&gt; script that calls out to the stubs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy0grhltu8qv2f0ishyzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy0grhltu8qv2f0ishyzd.png" alt="pre-push.d scripts being invoked from a pre-push script"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To solve the second problem, we'll be careful to fail the pre-push as soon as one of the stubs fails and to be thoughtful about the error message we emit if it does fail.&lt;/p&gt;

&lt;p&gt;We can solve the third problem by creating a simple &lt;code&gt;./scripts/install-pre-push&lt;/code&gt; script that our teammates can run. Recall that there's no simple way to have hooks automatically added to &lt;code&gt;.git/hook&lt;/code&gt; when a user clones a repository. So, you'll need to ask them to run this script when they're doing developer onboarding.&lt;/p&gt;

&lt;p&gt;Our installation script will do a little cleanup in &lt;code&gt;.git/hooks&lt;/code&gt; to ensure no old version of hooks will cause a conflict. Then, it will simply create a symbolic link from our &lt;code&gt;./scripts/pre-push&lt;/code&gt; script into &lt;code&gt;.git/hooks&lt;/code&gt;. This way the developer will always run the latest &lt;code&gt;pre-push&lt;/code&gt; script contents without having to re-run &lt;code&gt;./scripts/install-pre-push&lt;/code&gt; each time there's an update to its logic.&lt;/p&gt;

&lt;h1&gt;
  
  
  The installation script
&lt;/h1&gt;

&lt;p&gt;The &lt;a href="https://gist.github.com/jamesonwilliams/27435255ba543c9b9f407b1a61d750ca" rel="noopener noreferrer"&gt;&lt;code&gt;install-pre-push&lt;/code&gt;&lt;/a&gt; script looks as below. Note that it mostly does cleanup, and then creates a symbolic link.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Print a message and kill the script.&lt;/span&gt;
die&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 1&amp;gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Finds the top of the repo.&lt;/span&gt;
find_git_repo_top&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;current_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;# Loop until reaching the root directory "/"&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="c"&gt;# Check if ".git" directory exists&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_dir&lt;/span&gt;&lt;span class="s2"&gt;/.git"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;return
        fi&lt;/span&gt;

        &lt;span class="c"&gt;# Move up one directory&lt;/span&gt;
        &lt;span class="nv"&gt;current_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;done&lt;/span&gt;

    &lt;span class="c"&gt;# If ".git" directory is not found&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Git repository not found."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Ask the user a yes/no question and await their response. Return 0 if&lt;/span&gt;
&lt;span class="c"&gt;# they say yes (in some format).&lt;/span&gt;
await_yes_no&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; answer
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$answer&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="o"&gt;[&lt;/span&gt;yY]|[yY][eE][sS]&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nb"&gt;echo &lt;/span&gt;0
            &lt;span class="p"&gt;;;&lt;/span&gt;
        &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nb"&gt;echo &lt;/span&gt;1
            &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Delete whatever hooks may be active in the .git/hooks directory. This&lt;/span&gt;
&lt;span class="c"&gt;# may include things like the old pre-commit hook we had been using&lt;/span&gt;
delete_existing_hooks_with_confirmation&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;project_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;project_root&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.git/hooks/"&lt;/span&gt; &lt;span class="nt"&gt;-mindepth&lt;/span&gt; 1 &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.sample"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Found hook files: &lt;/span&gt;&lt;span class="nv"&gt;$hooks&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"OK to delete? [Y/n]"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;await_yes_no&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;die &lt;span class="s2"&gt;"OK; aborting."&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;$hooks&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; 

&lt;span class="c"&gt;# Install the pre-push script and any hooks found under the ./scripts&lt;/span&gt;
&lt;span class="c"&gt;# directory.&lt;/span&gt;
install_pre_push_hooks&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;project_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Installing scripts into .git/hooks ..."&lt;/span&gt;
    &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;project_root&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.git/hooks"&lt;/span&gt;
    &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;project_root&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/scripts/pre-push"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;project_root&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.git/hooks/pre-push"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Installs pre-push scripts after ensuring we're running from the&lt;/span&gt;
&lt;span class="c"&gt;# directory root, and after cleaning up any old git hook scripts.&lt;/span&gt;
main&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;project_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_git_repo_top&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    delete_existing_hooks_with_confirmation &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$project_root&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    install_pre_push_hooks &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$project_root&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

main


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

&lt;/div&gt;
&lt;h1&gt;
  
  
  The pre-push script
&lt;/h1&gt;

&lt;p&gt;The pre-push script is also pretty simple. Its primary job is to find the stub files in &lt;code&gt;./scripts/pre-push.d&lt;/code&gt;, and then send them the same arguments that Git sent to it.&lt;/p&gt;

&lt;p&gt;For a pre-push script, git will send four pieces of information to the hook - and possibly multiple times, for each reference being pushed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;localname&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;localhash&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;remotename&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;remotehash&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most of the time, you'll run something like:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

git push origin my-branch


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

&lt;/div&gt;

&lt;p&gt;In this case, &lt;code&gt;remotename&lt;/code&gt; and &lt;code&gt;remotehash&lt;/code&gt; won't be populated with meaningful info, and the four parameters will only arrive once.&lt;/p&gt;

&lt;p&gt;It is possible, however, to run more complex push commands such as &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

git push origin my-branch-1:refs/heads/target-1 my-branch-2:refs/heads/target-2


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

&lt;/div&gt;

&lt;p&gt;This is useful for us to understand the meaning of the four arguments, but as we'll see later, we won't need them. Most of our practical hooks will only consider the &lt;code&gt;localhash&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Don't write actual logic in this file. This file just fans out to&lt;/span&gt;
&lt;span class="c"&gt;# .git/hooks/pre-push.d/&amp;lt;your_file&amp;gt;, so that we can add a number of&lt;/span&gt;
&lt;span class="c"&gt;# checks into one place. This file aims to honor the original pre-push&lt;/span&gt;
&lt;span class="c"&gt;# contract and fan it out to stub files.&lt;/span&gt;

&lt;span class="c"&gt;# Finds the top of the repo.&lt;/span&gt;
find_git_repo_top&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;current_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;# Loop until reaching the root directory "/"&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="c"&gt;# Check if ".git" directory exists&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_dir&lt;/span&gt;&lt;span class="s2"&gt;/.git"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;return
        fi&lt;/span&gt;

        &lt;span class="c"&gt;# Move up one directory&lt;/span&gt;
        &lt;span class="nv"&gt;current_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;done&lt;/span&gt;

    &lt;span class="c"&gt;# If ".git" directory is not found&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Git repository not found."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;project_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_git_repo_top&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;project_root&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/scripts/pre-push.d &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.sw*"&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# pre-push.d receives four pieces of information for each source-target&lt;/span&gt;
&lt;span class="c"&gt;# push that may be in play. For exmaple, origin-&amp;gt;refs/heads/origin, and&lt;/span&gt;
&lt;span class="c"&gt;# my_kooll-&amp;gt;refs/heads/my_kool_branch would cause two iterations of this&lt;/span&gt;
&lt;span class="c"&gt;# while loop.&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;localname localhash remotename remotehash&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c"&gt;# For each set of push data, iterate over the hooks in alphabetical&lt;/span&gt;
    &lt;span class="c"&gt;# order. Pass the hook data in using the same contract.&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;hook &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$hooks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$localname&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$localhash&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$remotename&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$remotehash&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | bash &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$hook&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nv"&gt;RESULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$RESULT&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;fi
    done
done

&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0


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

&lt;/div&gt;
&lt;h1&gt;
  
  
  A pre-push.d script
&lt;/h1&gt;

&lt;p&gt;The more of these you see and write, you realize there are lots of edge cases. So let's consider our ultimate goal here. &lt;/p&gt;

&lt;p&gt;The branch we want to keep in good condition is &lt;code&gt;origin&lt;/code&gt;'s &lt;code&gt;main&lt;/code&gt;. In trunk-based development, we don't care so much about other branches. So as I mentioned above, let's not worry about the &lt;code&gt;remotehash&lt;/code&gt; and &lt;code&gt;remotename&lt;/code&gt;; let's always compare our local changes against &lt;code&gt;origin/main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another concern is performance. Ideally, we'd like to use &lt;em&gt;work avoidance&lt;/em&gt; to skip these validations entirely if there have been no relevant changes in the ref being pushed. And even when there &lt;em&gt;are&lt;/em&gt; changes between &lt;code&gt;origin/main&lt;/code&gt; and the ref to be pushed, we'd like to limit the validations to only those changes, if possible. So, we need some infrastructure for identifying the changed files.&lt;/p&gt;

&lt;p&gt;Lastly, we'd like to keep the user informed about what checks are being run and print out information about what state we've left the tree in. If we can auto-correct some of the issues, that would be a nice thing to do, too.&lt;/p&gt;

&lt;p&gt;The script below will run &lt;a href="https://github.com/facebook/ktfmt" rel="noopener noreferrer"&gt;&lt;code&gt;ktfmt&lt;/code&gt;&lt;/a&gt; over a Kotlin codebase and fail the push if any of the files are not well-formatted. It does also use a custom Gradle plugin we built around ktfmt, which accepts a &lt;code&gt;--run-over&lt;/code&gt; change set. This script will leave the fixed formatting changes in the tree, awaiting the developer to intervene, commit, and try again.&lt;/p&gt;

&lt;p&gt;The script below can be trivially adapted to any other number of tools, and you can include a few such scripts in your &lt;code&gt;./scripts/pre-push.d&lt;/code&gt;. For example, my current codebase has one for &lt;code&gt;ktfmt&lt;/code&gt;, and a very similar one for &lt;a href="https://github.com/square/gradle-dependencies-sorter" rel="noopener noreferrer"&gt;Square's Gradle Dependencies Sorter&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# This script looks for Kotlin files that have been changed locally and&lt;/span&gt;
&lt;span class="c"&gt;# ensures that they are correctly formatted according to ktfmt. This&lt;/span&gt;
&lt;span class="c"&gt;# script makes an effort to only run on the smallest possible set of&lt;/span&gt;
&lt;span class="c"&gt;# changed files as opposed to running broadly over the codebase, to&lt;/span&gt;
&lt;span class="c"&gt;# improve pre-push performance.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# The script takes the following steps:&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;#  1. Figure out the name of the remote for your repo on&lt;/span&gt;
&lt;span class="c"&gt;#     GitHub. If there is no remote (via `git remote`) add one called&lt;/span&gt;
&lt;span class="c"&gt;#     "your_company"&lt;/span&gt;
&lt;span class="c"&gt;#  2. Fetch the latest `main` ref from that remote.&lt;/span&gt;
&lt;span class="c"&gt;#  3. For any commits that are about to be pushed (git push can push&lt;/span&gt;
&lt;span class="c"&gt;#     multiple references at once), do the following steps, 4-8:&lt;/span&gt;
&lt;span class="c"&gt;#  4. Find an ancestor commit that is before both origin/main and the&lt;/span&gt;
&lt;span class="c"&gt;#     commit you're trying to push.&lt;/span&gt;
&lt;span class="c"&gt;#  5. Compute a list of .kt or .kts files that have changed between that&lt;/span&gt;
&lt;span class="c"&gt;#     ancestor and the commit being pushed&lt;/span&gt;
&lt;span class="c"&gt;#  6. Run ./gradlew ktfmtFormatPartial on only those files using --run-over=&amp;lt;list&amp;gt;.&lt;/span&gt;
&lt;span class="c"&gt;#  7. If there are no formatting changes applied, proceed to push.&lt;/span&gt;
&lt;span class="c"&gt;#     Otherwise, print a descriptive error message noting that files&lt;/span&gt;
&lt;span class="c"&gt;#     have been formatted and that the user will need to commit manually&lt;/span&gt;
&lt;span class="c"&gt;#     and push.&lt;/span&gt;

&lt;span class="c"&gt;# Print the name of the configured remote (usually "origin")&lt;/span&gt;
expected_remote&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    git remote &lt;span class="nt"&gt;-v&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/git@github.com:Your-org\/your-repo.git \(fetch\)/ { print $1 }'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Returns the name of the your_company origin. If it is not found locally,&lt;/span&gt;
&lt;span class="c"&gt;# we'll add one called "your_company" (conservative name so it doesn't clash&lt;/span&gt;
&lt;span class="c"&gt;# with whatever else you have going on.)&lt;/span&gt;
ensure_remote_installed&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;remote_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;expected_remote&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remote_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;git remote add your_company &lt;span class="s2"&gt;"git@github.com:Your-org/your-repo.git"&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"your_company"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remote_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Fetches, but does not apply, the remote references from GitHub's copy&lt;/span&gt;
&lt;span class="c"&gt;# of the project.&lt;/span&gt;
fetch_remote_refs&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;remote_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    git fetch &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remote_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; main &amp;amp;&amp;gt;/dev/null
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Computes a list of the names of the files that have changed between&lt;/span&gt;
&lt;span class="c"&gt;# two commit hashes.&lt;/span&gt;
compute_changed_files&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;from_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;to_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$from_hash&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$to_hash&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Determines if a file is a kotlin file.&lt;/span&gt;
is_kotlin_file&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file_path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ .kts?&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;0
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;1
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Computes which .kts? files have changed.&lt;/span&gt;
compute_changed_kotlins&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;from_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;to_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;changed_kotlins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;changed_file &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;compute_changed_files &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$from_hash&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$to_hash&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;is_kotlin_file &lt;span class="nv"&gt;$changed_file&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nv"&gt;changed_kotlins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$changed_file&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$changed_kotlins&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;fi
    done
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$changed_kotlins&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# This finds a common ancestor between origin/main and whatever commit&lt;/span&gt;
&lt;span class="c"&gt;# is being pushed. The idea here is that the local tree may not be&lt;/span&gt;
&lt;span class="c"&gt;# rebased onto origin/main itself, so we need to look backwards in&lt;/span&gt;
&lt;span class="c"&gt;# origin/main to find a commit that *is* in our history. This is the&lt;/span&gt;
&lt;span class="c"&gt;# developer's current marker for origin/main.&lt;/span&gt;
compute_from_hash&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;remote_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;local_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    git merge-base &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remote_name&lt;/span&gt;&lt;span class="s2"&gt;/main"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$local_hash&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Given two lists of strings compute the insersection of the two lists,&lt;/span&gt;
&lt;span class="c"&gt;# e.g., if A="foo bar", and B="foo", the intersection is "foo".&lt;/span&gt;
compute_intersection&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;intersection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$foo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        for &lt;/span&gt;b &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$bar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
            if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
                &lt;/span&gt;&lt;span class="nv"&gt;intersection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$intersection&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;fi
        done
    done

    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$intersection&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; | xargs
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Checks the result of the ktfmt task to see if formatted any files.  If&lt;/span&gt;
&lt;span class="c"&gt;# files were formatted, fail the hook and emit an error. If none were&lt;/span&gt;
&lt;span class="c"&gt;# formatted, continue to exit the hook successfully.&lt;/span&gt;
fail_if_any_formatted&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;changed_since_main&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;changed_after_fmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; | xargs&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;changed_in_both&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;compute_intersection &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$changed_since_main&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$changed_after_fmt&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$changed_in_both&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
The following files were not formatted correctly and have been fixed locally. Please commit them and try your push again.
&lt;/span&gt;&lt;span class="nv"&gt;$changed_in_both&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;        &lt;span class="nb"&gt;exit &lt;/span&gt;1
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Runs ktfmt over any changed .kts? files.&lt;/span&gt;
validate_ktfmt&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;remote_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;to_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;from_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;compute_from_hash &lt;span class="nv"&gt;$remote_name&lt;/span&gt; &lt;span class="nv"&gt;$to_hash&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="nv"&gt;changed_kotlins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;compute_changed_kotlins &lt;span class="nv"&gt;$from_hash&lt;/span&gt; &lt;span class="nv"&gt;$to_hash&lt;/span&gt; | xargs&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$changed_kotlins&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
    &lt;span class="k"&gt;fi

    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Running ktfmtFormatPartial over changed Kotlin files: &lt;/span&gt;&lt;span class="nv"&gt;$changed_kotlins&lt;/span&gt;&lt;span class="s2"&gt; ..."&lt;/span&gt;
    ./gradlew ktfmtFormatPartial &lt;span class="nt"&gt;--run-over&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$changed_kotlins&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null
    fail_if_any_formatted &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$changed_kotlins&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;remote_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ensure_remote_installed&lt;span class="si"&gt;)&lt;/span&gt;
fetch_remote_refs &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remote_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;localname localhash remotename remotehash&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;validate_ktfmt &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remote_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$localhash&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0


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

&lt;/div&gt;

&lt;p&gt;In my opinion, the most interesting part of this script is really the &lt;code&gt;git merge-base&lt;/code&gt; call, used to compute a common ancestor between &lt;code&gt;origin/main&lt;/code&gt; and the current ref. This is useful since you may not be rebased onto the remote's main when the hook runs. But if &lt;code&gt;origin/main&lt;/code&gt; is always correct, then the diff that is presented between &lt;code&gt;origin/main&lt;/code&gt; and your ref will encapsulate the entirety of your responsibility.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Well, there you have it. After writing a few of these, I put my "best of" playlist up for all to see. Each of these scripts can be found in &lt;a href="https://gist.github.com/jamesonwilliams/27435255ba543c9b9f407b1a61d750ca" rel="noopener noreferrer"&gt;this Gist&lt;/a&gt;. Let me know if you have a better way of doing this, or any ideas for improvements!&lt;/p&gt;

</description>
      <category>git</category>
    </item>
    <item>
      <title>Should You Adopt a Cross-Platform Client Framework?</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Wed, 13 Dec 2023 02:07:25 +0000</pubDate>
      <link>https://dev.to/jameson/should-you-adopt-a-cross-platform-client-framework-3hco</link>
      <guid>https://dev.to/jameson/should-you-adopt-a-cross-platform-client-framework-3hco</guid>
      <description>&lt;p&gt;Thesis: the decision to use a cross-platform framework is mostly a business one, and somewhat a technical one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Is Being Solved?
&lt;/h2&gt;

&lt;p&gt;Recently I've been building spreadsheets with the goal of comparing different aspects of iOS, Android, and the web. For example, here's a high-level comparison of the basic modern tools, languages, frameworks, and architectures employed on each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Android Native&lt;/th&gt;
&lt;th&gt;iOS Native&lt;/th&gt;
&lt;th&gt;ReactJS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IDE (Development Environment)&lt;/td&gt;
&lt;td&gt;Android Studio&lt;/td&gt;
&lt;td&gt;XCode&lt;/td&gt;
&lt;td&gt;VS Code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project Creation Template&lt;/td&gt;
&lt;td&gt;Android Studio templates&lt;/td&gt;
&lt;td&gt;Xcode templates&lt;/td&gt;
&lt;td&gt;npx create-vite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;Swift&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI Framework&lt;/td&gt;
&lt;td&gt;Jetpack&lt;/td&gt;
&lt;td&gt;SwiftUI&lt;/td&gt;
&lt;td&gt;ReactJS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test Framework&lt;/td&gt;
&lt;td&gt;JUnit&lt;/td&gt;
&lt;td&gt;XCTest&lt;/td&gt;
&lt;td&gt;Jest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST/Networking Tool&lt;/td&gt;
&lt;td&gt;Retrofit&lt;/td&gt;
&lt;td&gt;URLSession, Alamofire&lt;/td&gt;
&lt;td&gt;Axios, fetch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence/Database Library&lt;/td&gt;
&lt;td&gt;Room&lt;/td&gt;
&lt;td&gt;SwiftData&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lint Tool&lt;/td&gt;
&lt;td&gt;Android Lint, KTlint&lt;/td&gt;
&lt;td&gt;SwiftLint&lt;/td&gt;
&lt;td&gt;ESLint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reference Project on GitHub&lt;/td&gt;
&lt;td&gt;Now in Android&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Bulletproof React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DI Tool/Framework&lt;/td&gt;
&lt;td&gt;Dagger&lt;/td&gt;
&lt;td&gt;Needle&lt;/td&gt;
&lt;td&gt;React Context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build Tool&lt;/td&gt;
&lt;td&gt;Gradle&lt;/td&gt;
&lt;td&gt;Xcode Build System&lt;/td&gt;
&lt;td&gt;NPM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code Minifier&lt;/td&gt;
&lt;td&gt;R8&lt;/td&gt;
&lt;td&gt;ld&lt;/td&gt;
&lt;td&gt;Terser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asset Bundler&lt;/td&gt;
&lt;td&gt;AAPT&lt;/td&gt;
&lt;td&gt;actool, momc, ibtool&lt;/td&gt;
&lt;td&gt;Webpack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async Image Loading Tool&lt;/td&gt;
&lt;td&gt;AsyncImage (Coil)&lt;/td&gt;
&lt;td&gt;AsyncImage (SwiftUI)&lt;/td&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency Framework&lt;/td&gt;
&lt;td&gt;Coroutines&lt;/td&gt;
&lt;td&gt;async/await (Swift)&lt;/td&gt;
&lt;td&gt;async/await (JavaScript)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reactive Data Flow Framework&lt;/td&gt;
&lt;td&gt;Kotlin Flow&lt;/td&gt;
&lt;td&gt;Combine&lt;/td&gt;
&lt;td&gt;RxJS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Common UI Architecture&lt;/td&gt;
&lt;td&gt;MVVM&lt;/td&gt;
&lt;td&gt;MVVM&lt;/td&gt;
&lt;td&gt;Components&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In short, that's a lot for any one person to become an expert in. Most of us struggle just to develop expertise in a &lt;em&gt;single&lt;/em&gt; one of these platforms. So, as an engineering manager, you have essentially only three solutions to this problem.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hire specialists who can handle each platform&lt;/li&gt;
&lt;li&gt;Look for opportunities to "write once, run everywhere," so that you can do more with less&lt;/li&gt;
&lt;li&gt;Hire extremely senior people who have experience in all three platforms&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It doesn't take a spreadsheet to figure out that (1) and (3) are going to be more expensive in the short term: hiring more people--or more people who are also quite senior--is going to cost. As a result, (2) looks pretty attractive.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Tech Businesses Evolve
&lt;/h2&gt;

&lt;p&gt;We need to also discuss, for a moment, the natural evolution of a tech startup.&lt;/p&gt;

&lt;p&gt;Every business needs a website, and most businesses start with a web app. Websites run on every device, so you can get by with this for a bit, while you're proving viability (and thus, the ability to hire).&lt;/p&gt;

&lt;p&gt;In the US, once the business looks viable, it makes sense to build an iOS app. The US' Disposable Income class is mostly using iOS, and these are the folks you need to target to make money. Independent of any interesting technology choices, apps can attain significantly higher engagement than the web. For one, the continued presence of your little launcher icon on an end user's device is free marketing. And more importantly, Push Notifications are the modern replacement of web marketing emails. Even many established apps continue to send notifications that are barely better than this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Have you thought of our product today? Open the app to engage and monetize."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The last client that US businesses build, when they've entered the growth/scale phase, is an Android app. As noted above, the Disposable Income class is mostly using iOS. And, it's also true that there are slightly fewer Android users in the US, anyway. All that aside, it is nonetheless true that you &lt;strong&gt;can&lt;/strong&gt; greatly expand your addressable mobile market in the US by building an Android app. So US companies do. More launcher icons, more push notifications, more moolah.&lt;/p&gt;

&lt;p&gt;Mid-late growth phase businesses are Android businesses. Outside of the US, UK, Canada, and Australia, everyone uses an Android phone. If you want to make money in Europe, Asia, Latin America, or Africa, you are in the Android business.&lt;/p&gt;

&lt;p&gt;These phases of evolution are important to keep in mind because technical decisions (and hiring) in an engineering department often track these phases. Explicitly: you will likely make different choices about client platforms depending on which stage your business is in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are the Options?
&lt;/h2&gt;

&lt;p&gt;At a high level, you can either use cross-platform frameworks or not. Let's talk about the options that exist within the "okay, let's use them" category. There are three cross-platform frameworks worth considering right now: React Native, Flutter, and Kotlin Multiplatform. We can compare these along similar vectors to what we did above:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;KMP&lt;/th&gt;
&lt;th&gt;Flutter&lt;/th&gt;
&lt;th&gt;React Native&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IDE (Development Environment)&lt;/td&gt;
&lt;td&gt;IntelliJ IDEA&lt;/td&gt;
&lt;td&gt;VS Code or IntelliJ IDEA&lt;/td&gt;
&lt;td&gt;VS Code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project Creation Template&lt;/td&gt;
&lt;td&gt;&lt;a href="https://kmp.jetbrains.com/"&gt;KMP Project Generator&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;flutter create&lt;/code&gt; command&lt;/td&gt;
&lt;td&gt;react-native init&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;Dart&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI Framework&lt;/td&gt;
&lt;td&gt;Jetpack Compose, SwiftUI, React&lt;/td&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test Framework&lt;/td&gt;
&lt;td&gt;Kotlin Test&lt;/td&gt;
&lt;td&gt;flutter_test library&lt;/td&gt;
&lt;td&gt;Jest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST/Networking Tool&lt;/td&gt;
&lt;td&gt;Ktor&lt;/td&gt;
&lt;td&gt;Dio, http&lt;/td&gt;
&lt;td&gt;Axios, fetch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence/Database Library&lt;/td&gt;
&lt;td&gt;SQLDelight&lt;/td&gt;
&lt;td&gt;sqflite&lt;/td&gt;
&lt;td&gt;AsyncStorage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lint Tool&lt;/td&gt;
&lt;td&gt;Ktlint&lt;/td&gt;
&lt;td&gt;Dart Linter&lt;/td&gt;
&lt;td&gt;ESLint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reference Project on GitHub&lt;/td&gt;
&lt;td&gt;People In Space&lt;/td&gt;
&lt;td&gt;Flutter Samples&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DI Tool/Framework&lt;/td&gt;
&lt;td&gt;Koin&lt;/td&gt;
&lt;td&gt;get_it&lt;/td&gt;
&lt;td&gt;React Context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build Tool&lt;/td&gt;
&lt;td&gt;Gradle&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;flutter build&lt;/code&gt; command&lt;/td&gt;
&lt;td&gt;Metro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code Minifier&lt;/td&gt;
&lt;td&gt;Platform-specific&lt;/td&gt;
&lt;td&gt;Part of &lt;code&gt;flutter build&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Metro Bundler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asset Bundler&lt;/td&gt;
&lt;td&gt;Platform-specific&lt;/td&gt;
&lt;td&gt;Part of &lt;code&gt;flutter build&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Metro Bundler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async Image Loading Tool&lt;/td&gt;
&lt;td&gt;Platform-specific&lt;/td&gt;
&lt;td&gt;Image.network()&lt;/td&gt;
&lt;td&gt;Image (React Native)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency Framework&lt;/td&gt;
&lt;td&gt;Coroutines&lt;/td&gt;
&lt;td&gt;Dart async/await&lt;/td&gt;
&lt;td&gt;JavaScript Promises&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reactive Data Flow Framework&lt;/td&gt;
&lt;td&gt;Flow&lt;/td&gt;
&lt;td&gt;RxDart&lt;/td&gt;
&lt;td&gt;RxJS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Common UI Architecture&lt;/td&gt;
&lt;td&gt;MVI&lt;/td&gt;
&lt;td&gt;Widgets (MVU)&lt;/td&gt;
&lt;td&gt;Components (MVU)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  React Native
&lt;/h3&gt;

&lt;p&gt;Since every business starts with a stock of web developers, React Native is an attractive option since it's so similar to React.js. Your web engineers have to reach outside their comfort area a little bit, but can essentially be redeployed to mobile with a similar skill set. If your startup is &lt;em&gt;not&lt;/em&gt; able to hire iOS specialists in the near term, this is a pretty good pathway to get those Push Notification bucks.&lt;/p&gt;

&lt;p&gt;But React Native can be slow. And if your app needs any native iOS functionality, the bridging code can get unruly. It's also true that it's easier to hire native iOS engineers than it is to hire a web engineer and then train them to work on an iPhone. The closer your stay to the "default" platform recommended by Apple, the easier it will be to hire for iOS in the future, too. In short: if you have funding to scale your Engineering team over the next 6-12 months, and know that you'll be able to staff iOS engineers, don't mess around with RN.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flutter
&lt;/h3&gt;

&lt;p&gt;Flutter's Dart language gets compiled directly down to ARM machine code, which avoids one of React Native's biggest gripes. However, whoever is writing this app will need to learn Dart. This either means hiring specialists (we were trying to avoid this, right?) or having your web/iOS folks learn Dart. Fortunately, Dart is kind of like if TypeScript and Java had a bastard child. So, it's fairly accessible to a broader technical audience.&lt;/p&gt;

&lt;p&gt;Like React Native, Flutter has a pretty good story around &lt;em&gt;web&lt;/em&gt; interop. For these reasons, it wouldn't be outrageous to &lt;em&gt;start&lt;/em&gt; a company with Flutter. You could have web, iOS-- and Android for free. However, most companies don't start with Flutter, they start with React.js on the web. Adopting Flutter once you already have a large React.js investment makes less sense: you'd probably either pick React Native for its similarity or just create a discrete iOS codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kotlin Multiplatform
&lt;/h3&gt;

&lt;p&gt;Let me start by stating full-throatedly that Kotlin is the best front-end language. Line break.&lt;/p&gt;

&lt;p&gt;I am saying this as an Android engineer, yes, but StackOverflow agrees with me. TypeScript is a nice improvement to JavaScript, but it is still a weird bolt-on language. Swift is 5,000 times better than Objective-C and is almost as good as Kotlin. But, Kotlin is a little newer. It had the benefit of learning from these languages, iterating on their weaknesses, and yielding a more polished result.&lt;/p&gt;

&lt;p&gt;At the moment, I don't think it makes any sense to build web products with KMP, although it may in the future. There are really two camps that would think seriously about adopting KMM in my view:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;International startups that built an Android app first, and are now expanding to iOS&lt;/li&gt;
&lt;li&gt;American startups that have a web app, and are at their "how do we do mobile?" inflection point.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;KMM makes a lot of sense outside the US, where the mobile economics are different. If you &lt;em&gt;start&lt;/em&gt; with a modern Kotlin Android app, building a SwiftUI shim in KMM would be an awesome way to expand your userbase -- while not investing fully.&lt;/p&gt;

&lt;p&gt;For the American web startup, KMM could still be attractive. KMM works in a progressive way, where you get to decide how much of a specific platform is implemented natively or not. So, you could decide "okay, we've got our React.js web app, but we're going to share &lt;em&gt;some&lt;/em&gt; code across iOS and Android in this new codebase." Business logic could be written in Kotlin (a beautiful language), while large portions of the apps are built in Compose and Swift/SwiftUI. This allows you to stay somewhat true to the platforms, making long-term hiring for mobile specialists easier, too. (Dart and RN don't have this benefit.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared Architecture, Separate Platforms
&lt;/h3&gt;

&lt;p&gt;The last option I want to talk about-- and indeed the one that I think is right for many monetized companies-- is to maintain three separate frontend codebases that share architectural similarities.&lt;/p&gt;

&lt;p&gt;When iOS, Android, and the Web first emerged, they were very different from one another. JavaScript is not much like Objective-C. And syntax aside, Java is not much like either of those two. jQuery isn't like UIKit, which isn't like writing XML for Android Views.&lt;/p&gt;

&lt;p&gt;But that isn't the case anymore. React.js, SwiftUI, and Jetpack Compose all agree on composable hierarchies of UI components using uni-directional dataflow. TypeScript, Swift, and Kotlin all share some broadly similar syntax and language constructs. So, a valid question to ask is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can we exploit the similarities in the three platforms to keep the codebases ideologically consistent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are some clear benefits to doing this. Firstly, it'll be easy to hire platform specialists in perpetuity, since you're not "doing anything weird" in your tech stack. Secondly, you'll have no performance hits, since everything is native. And thirdly, there'll be no added complexity on any individual platform when bridging from the cross-platform framework into the native code.&lt;/p&gt;

&lt;p&gt;In practice, there are unique technical challenges to either approach - aligning three platforms through organizational cohesion or aligning three platforms through cross-platform frameworks. Cross-functional collaboration with your UX and Product designers becomes increasingly important when you operate in multiple codebases: the Engineering team needs to be using the same component names as the UX team, to stay aligned. &lt;/p&gt;

&lt;h2&gt;
  
  
  When to Choose Each
&lt;/h2&gt;

&lt;p&gt;"It depends," is usually a copout of an answer. So I'm going to make some opinionated suggestions.&lt;/p&gt;

&lt;p&gt;If you're starting from scratch and need to prove viability, either start with a React web app or start with Flutter, which are acceptable web solutions.&lt;/p&gt;

&lt;p&gt;Next, if you didn't pick Flutter and are figuring out how to expand into mobile, you should pick:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;React Native, if you don't have a line of sight to fund specialists on your Engineering team over the next year.&lt;/li&gt;
&lt;li&gt;KMM, if you are in a market where Android dominates, and will enter the market on Android first.&lt;/li&gt;
&lt;li&gt;A native iOS app, if you are American, and will enter the market on iOS first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;More nuanced situations arise for SMBs, who already have some combination of the technologies above. But, generally speaking, if you are growing as a business and Engineering team, you should be migrating away from cross-platform frameworks into discrete native codebases that share similar architecture and concepts.&lt;/p&gt;

&lt;p&gt;If you are a revenue-stable US SMB, and iOS is an important portfolio driver, your best investments are probably in native iOS or React Native, depending on the context of your web staff and product. If you are a revenue-stable SMB outside the US, your best investment is probably KMM.&lt;/p&gt;

&lt;p&gt;There are yet other businesses that support &lt;em&gt;more&lt;/em&gt; client platforms: for example, Netflix, Crunchyroll, etc. need to support a half-dozen-or-so TVs and Gaming platforms. To the extent that these companies can envelope a functional web app into some kind of wrapper while maintaining acceptable media performance, that option will almost certainly be a good trade of engineering effort and complexity vs. end-user monetization per platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, What About Tech Factors?
&lt;/h2&gt;

&lt;p&gt;At the beginning of this article, I argued that multi-platform is a business decision. And, my argument has essentially been that you need to look at monetization of iOS, Web, Android, and compare it to your Engineering team's budget. If you have a lot of money, build native and don't look back. If you don't, you can compromise and optimize for the platforms that are most important to driving revenue in your business.&lt;/p&gt;

&lt;p&gt;But, technology can be an important aspect of "what drives revenue for your business?" If you intend to have a media-rich application with smooth animations, real-time widgets, and a high frame rate, you &lt;em&gt;may not be able to achieve your business goals&lt;/em&gt; with some of these choices. &lt;/p&gt;

&lt;p&gt;There are many high-profile deep dives around mobile teams entering and exiting the React Native arena - here are two well-known organizations that have come or gone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://medium.com/airbnb-engineering/sunsetting-react-native-1868ba28e30a"&gt;Discord - An Exciting Update to Discord For Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/airbnb-engineering/sunsetting-react-native-1868ba28e30a"&gt;Airbnb - Sunsetting React Native&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Anecdotally, Shopify shed some significant native mobile headcount through regrettable attrition when they adopted RN at their established business.&lt;/p&gt;

&lt;p&gt;For Flutter and KMP, it may be too early to have "here's our decision after a few years"-style articles from high-profile businesses. But, they also have their own naysayers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://betterprogramming.pub/why-flutter-isnt-the-next-big-thing-e268488521f4"&gt;Why Flutter Isn’t the Next Big Thing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.donnfelker.com/why-kotlin-multiplatform-wont-succeed/"&gt;Why Kotlin Multiplatform Won’t Succeed&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While it is true that all of RN, Flutter, and KMP support bridging into native code, this type of code tends to invalidate the productivity benefits of adopting a cross-platform framework to begin with. Not only are folks writing native code anyway, but they also have the added complexity of fighting an additional framework, too.&lt;/p&gt;

&lt;p&gt;Cross-platform is &lt;em&gt;always&lt;/em&gt; a financial or organizational shortcut, inherently more cumbersome than native development. And while there is some developer productivity to be gained in code reuse, the gains may not be linear with the size of an app or its Engineering team. On the other hand, shortcuts are sometimes necessary in business for survival. If your options are to ship your web app on iOS via RN, or to lose market opportunities on iOS for lack of staffing/expertise: that's an obvious choice.&lt;/p&gt;

&lt;p&gt;Well, that's it. Would love to hear your thoughts!&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>flutter</category>
      <category>kmm</category>
      <category>kmp</category>
    </item>
    <item>
      <title>Modern Async Primitives on iOS, Android, and the Web</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Wed, 06 Dec 2023 02:34:06 +0000</pubDate>
      <link>https://dev.to/jameson/modern-async-primitives-on-ios-android-and-the-web-5c8j</link>
      <guid>https://dev.to/jameson/modern-async-primitives-on-ios-android-and-the-web-5c8j</guid>
      <description>&lt;h1&gt;
  
  
  Let's Talk About Asynchronicity
&lt;/h1&gt;

&lt;p&gt;When you began your programming journey, you started with synchronous, serialized code. One operation follows another, and so on, along a single timeline.&lt;/p&gt;

&lt;p&gt;Soon after, you likely discovered the ability to execute code along multiple timelines. With this capability, your code might &lt;em&gt;still&lt;/em&gt; be synchronous, even if it splits its tasks across two timelines. But, your concurrent code could also be &lt;em&gt;asynchronous&lt;/em&gt;, meaning that your main timeline continues along while some results are deferred until later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3j03438z8h2ibim3xbe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3j03438z8h2ibim3xbe.png" alt="A comparison of synchronous and asynchronous program execution across two timelines"&gt;&lt;/a&gt;&lt;br&gt;
(Thanks &lt;a href="https://twitter.com/AlisdairBroshar" rel="noopener noreferrer"&gt;@AlisdairBroshar&lt;/a&gt; for the &lt;a href="https://www.koyeb.com/blog/introduction-to-synchronous-and-asynchronous-processing" rel="noopener noreferrer"&gt;image&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Arguably, a more interesting version of asynchronicity exists when multiple tasks are scheduled along a &lt;em&gt;single&lt;/em&gt; timeline. This is intuitively easy to understand: you go about your day on a single timeline, and you probably juggle many unrelated tasks as you do so. You have to &lt;em&gt;suspend&lt;/em&gt; one of your tasks to focus on the next one, and then the next one, etc. But unfortunately, those suspended tasks don't make any progress while you're not working on them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7v9y8ejzm057cy7lopv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7v9y8ejzm057cy7lopv.png" alt="Along a single timeline, a task must be suspended in order to work on something new"&gt;&lt;/a&gt;&lt;br&gt;
(Image &lt;a href="https://doordash.engineering/2021/11/09/the-beginners-guide-to-kotlin-coroutine-internals/" rel="noopener noreferrer"&gt;credit&lt;/a&gt; to Sonic Wang @ DoorDash Engineering)&lt;/p&gt;

&lt;p&gt;In this space, we also have the somewhat related term &lt;em&gt;blocking&lt;/em&gt;. Java's NIO library is one well-known &lt;em&gt;non-blocking&lt;/em&gt; tool used for managing multiple tasks on a single Java thread. When listening to sockets, most of the time a thread is just blocked, doing nothing until it receives some data. So, it's efficient to use a single thread for monitoring many sockets, to increase the likelihood of the thread having some actual work to do. The &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/nio/channels/Selector.html" rel="noopener noreferrer"&gt;&lt;code&gt;Selector&lt;/code&gt;&lt;/a&gt; API does this but is notoriously challenging to program well. Instead, developers use frameworks like &lt;a href="https://netty.io/" rel="noopener noreferrer"&gt;Netty&lt;/a&gt; which abstract some of NIO's complexity and layer on some best practices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4hfbpq5jbp7sxe5mvxu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4hfbpq5jbp7sxe5mvxu5.png" alt="Java NIO's Selector API allows efficient reading from multiple channels using a single thread"&gt;&lt;/a&gt;&lt;br&gt;
(Thanks &lt;a href="https://www.geeksforgeeks.org/introduction-to-java-nio-with-examples/#" rel="noopener noreferrer"&gt;GeeksForGeeks.org&lt;/a&gt; for the image.)&lt;/p&gt;

&lt;p&gt;In the current generation of programming languages, there has been a renewed effort to simplify asynchronicity through &lt;em&gt;structured concurrency&lt;/em&gt;. Language facilities like Swift's &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; or Kotlin's Coroutines allow for a unit of work to be suspended and resumed within the context of one or more timelines. Below, we're going to explore how some of these tools work.&lt;/p&gt;

&lt;h1&gt;
  
  
  Swift: async/await &amp;amp; AsyncSequence
&lt;/h1&gt;

&lt;p&gt;Swift's structured concurrency support was announced at Apple's WWDC '21 conference. &lt;a href="https://twitter.com/call1cc" rel="noopener noreferrer"&gt;Kavon&lt;/a&gt; @ Apple gives a great intro to the topic &lt;a href="https://developer.apple.com/videos/play/wwdc2021/10134/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Swift's &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; support allows you to yield a flow of execution in your program while awaiting the result of a task. One example given in Kavon's presentation is to download some data and then await the result:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// Note `async let` here! Two tasks run concurrently.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLSession&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;imageReq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URLSession&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metadataReq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Do other tasks here, while tasks execute&lt;/span&gt;
&lt;span class="k"&gt;guard&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;byPreparingThumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ofSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kt"&gt;ThumbnailFailedError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The beauty of this code is that all of the async work will be terminated if this block goes out of scope. This simplifies your mental model considerably and avoids entire classes of bugs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remark: the &lt;code&gt;data&lt;/code&gt; and &lt;code&gt;metadata&lt;/code&gt; objects above might look a lot like JavaScript &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" rel="noopener noreferrer"&gt;&lt;code&gt;Promise&lt;/code&gt;&lt;/a&gt;s, Rx &lt;a href="https://reactivex.io/documentation/single.html" rel="noopener noreferrer"&gt;&lt;code&gt;Single&lt;/code&gt;&lt;/a&gt;s, or Java &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html" rel="noopener noreferrer"&gt;&lt;code&gt;Future&lt;/code&gt;&lt;/a&gt;s, if you've seen any of those before. (We'll talk about Promises, later.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Swift &amp;gt;=5.5 also supports a new means of &lt;em&gt;unstructured&lt;/em&gt; concurrency, where you are responsible for governing the task lifecycle yourself, instead of letting Swift handle it implicitly.  For example, you can launch a Task to await the execution of some async function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;someAsyncFunction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;When using the &lt;code&gt;Task&lt;/code&gt; construct, you need to be careful to &lt;code&gt;cancel()&lt;/code&gt; the Task at the appropriate time when it goes out of scope.&lt;/p&gt;

&lt;p&gt;Beyond these single-result-oriented constructs, Swift &amp;gt;=5.5 also supports async operations over &lt;em&gt;collections&lt;/em&gt;. &lt;a href="https://developer.apple.com/documentation/swift/asyncsequence" rel="noopener noreferrer"&gt;&lt;code&gt;AsyncSequence&lt;/code&gt;&lt;/a&gt;, also &lt;a href="https://developer.apple.com/videos/play/wwdc2021/10058/" rel="noopener noreferrer"&gt;announced at WWDC21&lt;/a&gt; builds on earlier streamed value solutions like &lt;a href="https://github.com/ReactiveX/RxSwift/blob/main/Documentation/GettingStarted.md#observables-aka-sequences" rel="noopener noreferrer"&gt;RxSwift's &lt;code&gt;Observable&lt;/code&gt;&lt;/a&gt; or Combine's &lt;a href="https://developer.apple.com/documentation/combine/passthroughsubject" rel="noopener noreferrer"&gt;&lt;code&gt;PassthroughSubject&lt;/code&gt;&lt;/a&gt;. But unlike those constructs, &lt;code&gt;AsyncSequence&lt;/code&gt; can yield execution between iterations.&lt;/p&gt;

&lt;p&gt;Let's see in action. First, let's take some ordinary for loops and wrap them in &lt;code&gt;Task&lt;/code&gt;s. As noted above, the &lt;code&gt;Task&lt;/code&gt; object will provide an execution context on the main &lt;a href="https://developer.apple.com/videos/play/wwdc2021/10133/" rel="noopener noreferrer"&gt;actor&lt;/a&gt;, in which async work could occur. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;@MainActor&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;@MainActor&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;As you would expect, since there isn't any use of &lt;code&gt;async&lt;/code&gt; anywhere, these just run sequentially. The output is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;

1
2
3
10
20
30


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

&lt;/div&gt;

&lt;p&gt;Now, instead, let's try changing the array to an &lt;code&gt;AsyncSequence&lt;/code&gt;, and running it again. Importantly, everything will still be on the main actor.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note for the code below: &lt;code&gt;.publisher&lt;/code&gt; transforms the array into a Combine publisher, and &lt;code&gt;.values&lt;/code&gt; creates an &lt;code&gt;AsyncSquence&lt;/code&gt; from that Publisher.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;@MainActor&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;@MainActor&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This time, the loops yield execution after each value is emitted. The two tasks end up interleaving their output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;

1
10
2
20
3
30


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

&lt;/div&gt;

&lt;p&gt;The two tasks collaborate on a single thread of execution, almost as if they were operating concurrently. Pretty neat, right? &lt;/p&gt;

&lt;p&gt;One convenient place an &lt;code&gt;AsyncSequence&lt;/code&gt; shows up is in this short-hand to read a network request's data, line-by-line. This can be achieved by async-iterating over the &lt;code&gt;lines&lt;/code&gt; property on &lt;code&gt;URL&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;rickMortyApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://rickandmortyapi.com/api"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rickMortyApi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Another place you might encounter &lt;code&gt;AsyncSequence&lt;/code&gt; is in SwiftUI's property wrappers. For example, let's suppose you have a field &lt;code&gt;@Published var credentials: [Credential]&lt;/code&gt; in some &lt;code&gt;@ObservableObject&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservableObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
  &lt;span class="kd"&gt;@Published&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Credential&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;You could iterate over this value like so:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// use credential&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Side note, if you &lt;a href="https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro" rel="noopener noreferrer"&gt;Migrate from the Observable Object protocol to the Observable macro&lt;/a&gt;, a lot of your &lt;code&gt;$&lt;/code&gt; binding calls will probably go away, rendering this kind of iteration unnecessary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This new functionality is not without some open questions, though. In his excellent blog from April 2022, &lt;a href="https://johnoreilly.dev/posts/swift-async-algorithms-combine/" rel="noopener noreferrer"&gt;Using new Swift Async Algorithms package to close the gap on Combine&lt;/a&gt;, John O'Reilley notes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As developers have started adopting the new Swift Concurrency functionality introduced in Swift 5.5, a key area of interest has been around how this works with the Combine framework and how much of existing Combine-based functionality can be replaced with async/await, AsyncSequence etc. based code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In that blog, John makes a note of the &lt;a href="https://github.com/sideeffect-io/AsyncExtensions" rel="noopener noreferrer"&gt;AsyncExtensions&lt;/a&gt; library, which has several cool &lt;a href="https://github.com/sideeffect-io/AsyncExtensions#features" rel="noopener noreferrer"&gt;Features&lt;/a&gt; like &lt;code&gt;merge()&lt;/code&gt;, &lt;code&gt;zip()&lt;/code&gt;, etc., that you'd expect from other stream/collections API surfaces. &lt;/p&gt;

&lt;p&gt;You might also prefer to look at Apple's own evolution library for AsyncSequence, which offers a mostly-similar feature-set, called &lt;a href="https://github.com/apple/swift-async-algorithms#swift-async-algorithms" rel="noopener noreferrer"&gt;swift-async-algorithms&lt;/a&gt;. This library is more likely to be a conservative staging ground for forthcoming Swift language APIs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Kotlin: Coroutines, Flow
&lt;/h1&gt;

&lt;p&gt;Many ecosystems have adopted the &lt;a href="https://en.wikipedia.org/wiki/Async/await" rel="noopener noreferrer"&gt;&lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;&lt;/a&gt; style of structured concurrency, but Kotlin took a slightly different approach. &lt;a href="https://en.wikipedia.org/wiki/Coroutine" rel="noopener noreferrer"&gt;Coroutines&lt;/a&gt; is an older technology: the first usage of the term dates back to the late fifties and early sixties. The Kotlin implementation provides several improvements over prior implementations, however, such as native language integration, readability, structured concurrency, and error handling.&lt;/p&gt;

&lt;p&gt;While many languages use &lt;code&gt;async&lt;/code&gt; to denote an asynchronous function, Kotlin instead marks functions with &lt;code&gt;suspend&lt;/code&gt;. Similar to what we saw with &lt;code&gt;AsyncSequence&lt;/code&gt;, &lt;code&gt;suspend&lt;/code&gt; introduces a &lt;em&gt;suspension point&lt;/em&gt; where execution may be yielded.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getNetworkData&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... well, how bout it?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Just like Swift has support for structured concurrency, Kotlin can nest coroutines, too, s.t. canceling a top-level coroutine will also cancel its child coroutines. Let's consider an example. Below we use the &lt;code&gt;launch&lt;/code&gt; coroutine builder to initiate a parent coroutine, and another &lt;code&gt;launch&lt;/code&gt; coroutine builder to create a child coroutine within it.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="n"&gt;coroutineScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// When I am canceled,&lt;/span&gt;
  &lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// I will cancel, too.&lt;/span&gt;
    &lt;span class="nf"&gt;getNetworkData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;In Kotlin, the closest thing to Swift's &lt;code&gt;Task&lt;/code&gt; is &lt;code&gt;GlobalScope.launch&lt;/code&gt;, which can be used to initiate a coroutine that is not bound to its parent's scope.  Use of &lt;code&gt;GlobalScope&lt;/code&gt; is generally not recommended, since it will require you to carefully manage the lifecycle of the returned &lt;code&gt;Job&lt;/code&gt; object, calling &lt;code&gt;cancel()&lt;/code&gt; when appropriate.&lt;/p&gt;

&lt;p&gt;Consider the bit of code below as an example, wherein a coroutine is launched within another coroutine:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;inside&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="nc"&gt;GlobalScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;inside&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GlobalScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"An inside job!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;inside&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This code will print:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;

An inside job!


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

&lt;/div&gt;

&lt;p&gt;If you remove the &lt;code&gt;GlobalScope&lt;/code&gt; designation, then the code prints nothing, since &lt;code&gt;inside&lt;/code&gt; is automatically canceled.&lt;/p&gt;

&lt;p&gt;Kotlin also has a construct for asynchronous collections/streams. Kotlin's version of &lt;code&gt;AsyncSequence&lt;/code&gt; is called a &lt;code&gt;Flow&lt;/code&gt;. Just as Swift's &lt;code&gt;AsyncSequence&lt;/code&gt; builds upon prior experience with RxSwift and Combine, Kotlin's &lt;code&gt;Flow&lt;/code&gt; APIs build upon earlier stream/collection APIs in the JVM ecosystem: Java's &lt;a href="https://github.com/ReactiveX/RxJava#rxjava-reactive-extensions-for-the-jvm" rel="noopener noreferrer"&gt;RxJava&lt;/a&gt;, &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html" rel="noopener noreferrer"&gt;Java8 Streams&lt;/a&gt;, &lt;a href="https://projectreactor.io/" rel="noopener noreferrer"&gt;Project Reactor&lt;/a&gt;, and Scala's &lt;a href="https://akka.io/" rel="noopener noreferrer"&gt;Akka&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Let's see &lt;code&gt;Flow&lt;/code&gt; in action:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="nf"&gt;runBlocking&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;flowOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="nf"&gt;flowOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The code above is single-threaded. If we weren't using &lt;code&gt;Flow&lt;/code&gt;, we might expected the values to be emitted serially: 1,2,3,10,20,30. But, since each coroutine yields execution upon receiving a value, the outputs interleave:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;

1
10
2
20
3
30


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

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The code above uses an explicit &lt;code&gt;yield()&lt;/code&gt; operator to show where the code yields execution. As a technical note,  &lt;code&gt;collect { }&lt;/code&gt; itself does not suspend, without &lt;code&gt;yield()&lt;/code&gt;, even if the &lt;code&gt;Flow&lt;/code&gt; would otherwise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In practice, you find &lt;code&gt;Flow&lt;/code&gt;s used heavily between the View and View Model layers of most modern apps using MVVM/MVI design patterns. Views commonly collect state updates from View Models using &lt;code&gt;Flow&lt;/code&gt;s.&lt;/p&gt;

&lt;h1&gt;
  
  
  JavaScript: aysnc/await, Promises, AsyncIterator/AsyncGenerator
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; support has existed in Node.js and web browsers since 2017. Famously, &lt;code&gt;await&lt;/code&gt; can be used to wait for a &lt;code&gt;Promise&lt;/code&gt; to resolve or reject:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://rickandmortyapi.com/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Awaiting your data...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetchPromise&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The data has arrived: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;())()&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The code above works similarly to the unstructured concurrency primitives we saw with Swift's &lt;code&gt;Task&lt;/code&gt; and Kotlin's &lt;code&gt;GlobalScope.launch&lt;/code&gt;. The &lt;code&gt;fetch&lt;/code&gt; function initiates some asynchronous work, bundles it up into a &lt;code&gt;Promise&lt;/code&gt; object, and yields execution so the lines below it can proceed. Finally, we await the result of the Promise, and extract JSON data received from the network.&lt;/p&gt;

&lt;p&gt;Unlike Swift and Kotlin, JavaScript does &lt;em&gt;not&lt;/em&gt; have any native support for structured concurrency. In fact, it doesn't even have native support for canceling unstructured tasks. There could be various reasons for this: JavaScript was one of the first mainstream languages to adopt &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; and its interpreters have traditionally been single-threaded. Nevertheless, this oversight has compelled the ecosystem to innovate its own unique solutions. As a well-known example, Axios provides a &lt;a href="https://axios-http.com/docs/cancellation" rel="noopener noreferrer"&gt;&lt;code&gt;CancelToken&lt;/code&gt;&lt;/a&gt; to facilitate task cancellation within that library.&lt;/p&gt;

&lt;p&gt;However, JavaScript &lt;em&gt;does&lt;/em&gt; have async collection primitives analogous to those provided by Swift and Kotlin: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator" rel="noopener noreferrer"&gt;&lt;code&gt;AsyncIterator&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator" rel="noopener noreferrer"&gt;&lt;code&gt;AsyncGenerator&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Much like we saw with Swift's &lt;code&gt;AsyncSequence&lt;/code&gt;, adding the &lt;code&gt;await&lt;/code&gt; keyword into the generic &lt;code&gt;for&lt;/code&gt; loop will create a suspension point wherein the JavaScript interpreter can yield its execution to other tasks. (See &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of" rel="noopener noreferrer"&gt;&lt;code&gt;for await...of&lt;/code&gt;&lt;/a&gt; documentation from Mozilla.)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;asyncGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;asyncGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;asyncGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This code is single-threaded, but we again see two tasks using cooperative multitasking to yield execution to one another. The result is familiar:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;

&lt;p&gt;1&lt;br&gt;
10&lt;br&gt;
2&lt;br&gt;
20&lt;br&gt;
3&lt;br&gt;
30&lt;/p&gt;

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

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Wrapping Up&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;Well, there you have it: a broad overview of structured concurrency and asynchronous collections programming in Swift, Kotlin, and JavaScript.&lt;/p&gt;

&lt;p&gt;To help illustrate, here's a table comparing some of the machinery we discussed, across each of the three languages.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Swift&lt;/th&gt;
&lt;th&gt;Kotlin&lt;/th&gt;
&lt;th&gt;JavaScript&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Launch async work (structured)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;async let&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;launch { }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch async work (unstructured)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Task { }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GlobalScope.launch { }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Promise()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cancel async work (unstructured)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Task.cancel()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Job.cancel()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Await completion of work&lt;/td&gt;
&lt;td&gt;&lt;code&gt;await&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Implicit)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;await&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mark function as asynchronous&lt;/td&gt;
&lt;td&gt;&lt;code&gt;async&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;suspend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;async&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async collection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AsyncSequence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Flow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AsyncIterator&lt;/code&gt; / &lt;code&gt;AsyncGenerator&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async iteration syntax&lt;/td&gt;
&lt;td&gt;&lt;code&gt;for await x in seq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;flow.collect { }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;for await (let x of gen)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>async</category>
      <category>swift</category>
      <category>kotlin</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Splitting SwiftData and SwiftUI via MVVM</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Tue, 31 Oct 2023 22:20:54 +0000</pubDate>
      <link>https://dev.to/jameson/swiftui-with-swiftdata-through-repository-36d1</link>
      <guid>https://dev.to/jameson/swiftui-with-swiftdata-through-repository-36d1</guid>
      <description>&lt;p&gt;&lt;a href="https://developer.apple.com/xcode/swiftdata/" rel="noopener noreferrer"&gt;SwiftData&lt;/a&gt; was announced at WWDC in June 2023. Its primary goal is to enable data persistence in a SwiftUI app with as little fuss as possible.&lt;/p&gt;

&lt;p&gt;Most of the tutorials and snippets we see on the web show it being used directly in a SwiftUI View. For example, let's consider the minimal code to query, insert, and delete items from a list.&lt;/p&gt;

&lt;p&gt;Suppose we have a model of an item that we'll want to display in a list. For example, here's the &lt;code&gt;@Model&lt;/code&gt; that XCode generates if we select "use SwiftData" in the project template:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kd"&gt;@Model&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;With &lt;em&gt;very&lt;/em&gt; little code, we can use the &lt;code&gt;@Query&lt;/code&gt; property wrapper to read some existing items and show them in a list:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Query&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;FormatStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Of course, we'll also need some way to add and remove those items. For whatever reason, those actions happen through a separate &lt;code&gt;ModelContext&lt;/code&gt; object:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;modelContext&lt;/span&gt;
    &lt;span class="kd"&gt;@Query&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;VStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Add Item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;systemImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"plus"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                    &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;FormatStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;offsets&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;offsets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Above, we've included a simple button to add new &lt;code&gt;Item&lt;/code&gt;s, and a swipe-to-delete interaction that can delete &lt;code&gt;Item&lt;/code&gt;s. Here's what the UI ends up looking like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwe91fuj7jxdyhcwammys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwe91fuj7jxdyhcwammys.png" alt="A simple SwiftUI list showing add and delete actions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The magic here, of course, is that the items will persist even after the app is killed: that's the point of using SwiftData.&lt;/p&gt;

&lt;h1&gt;
  
  
  First Attempt at MVVM &amp;amp; SwiftData
&lt;/h1&gt;

&lt;p&gt;Let's now suppose that we want to split apart our code and introduce an MVVM pattern. Doing so will help us to create testable, reusable layers in our app.&lt;/p&gt;

&lt;p&gt;We have a few new challenges here, using SwiftData. Firstly, we'll need a way to get access to the &lt;code&gt;ModelContext&lt;/code&gt; outside of the SwiftUI View. Let's try a naive approach of using the same &lt;code&gt;@Environment&lt;/code&gt; property binding to access &lt;code&gt;ModelContext&lt;/code&gt; from our new &lt;em&gt;ViewModel&lt;/em&gt;, instead of the &lt;code&gt;ContentView&lt;/code&gt;. We might end up with something like below. Note that all data-management actions have been moved out of the &lt;code&gt;ContentView&lt;/code&gt; and now live in a new class &lt;code&gt;ViewModel&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;viewModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;VStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Add Item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;systemImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"plus"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                    &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;FormatStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;offsets&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;offsets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And a really simple-minded ViewModel to go with it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kd"&gt;@Observable&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ViewModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@ObservationIgnored&lt;/span&gt;
    &lt;span class="kd"&gt;@Environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;modelContext&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;appendItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;FetchDescriptor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;fatalError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localizedDescription&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;At this point, we try to run our simple app and we get this error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Accessing Environment&amp;lt;ModelContext&amp;gt;'s value outside of being installed on a View. This will always read the default value and will not update.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Darn.&lt;/p&gt;

&lt;h1&gt;
  
  
  Updating App integration
&lt;/h1&gt;

&lt;p&gt;Let's go back and look at our &lt;code&gt;App&lt;/code&gt; class for a moment. The simplest integration for SwiftData is to inject the &lt;code&gt;ModelContext&lt;/code&gt; into the &lt;code&gt;WindowGroup&lt;/code&gt;, like this, which makes it available to SwiftUI:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kd"&gt;@main&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;DataPlaygroundApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;Scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;WindowGroup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;modelContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;It's worth noting that &lt;a href="https://developer.apple.com/documentation/swiftdata/modelcontainer/maincontext" rel="noopener noreferrer"&gt;&lt;code&gt;mainContext&lt;/code&gt;&lt;/a&gt; is a property inside of a &lt;code&gt;ModelContainer&lt;/code&gt;. So, instead of accessing the &lt;code&gt;ModelContext&lt;/code&gt; via &lt;code&gt;@Environment&lt;/code&gt;, maybe our ViewModel could access it through the &lt;code&gt;ModelContainer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's remove that &lt;code&gt;modelContainer(...)&lt;/code&gt; line in the &lt;code&gt;App&lt;/code&gt; altogether and try a different approach. Instead of accessing it from the View layer &lt;em&gt;at all&lt;/em&gt;, let's create a data source class.&lt;/p&gt;

&lt;p&gt;Our App now looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kd"&gt;@main&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;DataPlaygroundApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;Scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;WindowGroup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h1&gt;
  
  
  Defining a Data Source
&lt;/h1&gt;

&lt;p&gt;Now, let's define an &lt;code&gt;ItemDataSource&lt;/code&gt; that will own the &lt;code&gt;ModelContainer&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ItemDataSource&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;modelContainer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ModelContainer&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ModelContext&lt;/span&gt;

    &lt;span class="kd"&gt;@MainActor&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;shared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ItemDataSource&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;@MainActor&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try!&lt;/span&gt; &lt;span class="kt"&gt;ModelContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modelContainer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mainContext&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;appendItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;fatalError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localizedDescription&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;FetchDescriptor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;fatalError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localizedDescription&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;A few things to notice here. The class uses &lt;code&gt;@MainActor&lt;/code&gt;, since we must access &lt;code&gt;mainContext&lt;/code&gt; from the main actor. If you &lt;em&gt;don't&lt;/em&gt; include &lt;code&gt;@MainActor&lt;/code&gt;, you'll get a compile error like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Main actor-isolated property 'mainContext' can not be referenced from a non-isolated context&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also, I've made the component a singleton so we only have one instance of the &lt;code&gt;ModelContainer&lt;/code&gt;. (There are many other ways to achieve this, this was easy for demonstration purposes.)&lt;/p&gt;

&lt;p&gt;Now, our simple &lt;code&gt;ViewModel&lt;/code&gt; just calls out to the data source. When &lt;code&gt;items&lt;/code&gt; changes, since the &lt;code&gt;ViewModel&lt;/code&gt; is &lt;code&gt;@Observable&lt;/code&gt;, the &lt;code&gt;ContentView&lt;/code&gt; will automatically respond to it and render the new items.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;

&lt;span class="kd"&gt;@Observable&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ViewModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@ObservationIgnored&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ItemDataSource&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ItemDataSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ItemDataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;appendItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And there you have it! You can re-use the &lt;code&gt;ItemDataSource&lt;/code&gt; in any number of your SwiftUI View/ViewModels, and you can start building up tests around each component in isolation.&lt;/p&gt;

&lt;p&gt;Let me know in the comments what you think of this approach.&lt;/p&gt;

</description>
      <category>swiftui</category>
      <category>ios</category>
      <category>swift</category>
      <category>swiftdata</category>
    </item>
    <item>
      <title>Simple Runtime Feature Gating on Android</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Sat, 29 Oct 2022 11:21:59 +0000</pubDate>
      <link>https://dev.to/jameson/simple-runtime-feature-gating-on-android-oh0</link>
      <guid>https://dev.to/jameson/simple-runtime-feature-gating-on-android-oh0</guid>
      <description>&lt;p&gt;I recently saw a tweet that got me thinking:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1586230874915409921-312" src="https://platform.twitter.com/embed/Tweet.html?id=1586230874915409921"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1586230874915409921-312');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1586230874915409921&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;The key point here is absolutely true. On the backend or frontend, you can deploy code whenever you want, all day every day. Your users will instantly get these updates.&lt;/p&gt;

&lt;p&gt;That's not how it works with mobile apps. If you want to roll an updated version of your app, you'll at minimum need to upload the new version to Google or Apple, and await their review/acceptance.&lt;/p&gt;

&lt;p&gt;If you work on a mobile app team, you're probably doing a weekly or maybe bi-weekly release. In this environment, it'll take even longer before you can get a fix out. &lt;/p&gt;

&lt;p&gt;But even if you could update your app every time you make a change - should you? No. Each update you make is potentially an annoyance for the mobile app user, and a waste of their bandwidth if you're updating too frequently.&lt;/p&gt;




&lt;p&gt;"Rollback" refers to pulling a defective deployment and going back to the last known working version. In the Google Play and Apple App stores, there is nothing exactly like this. So, the common approach to rollback involves re-publishing the content of an old release, but with a new, later version number. It is in fact a new release artifact, it just functions like an older one.&lt;/p&gt;

&lt;p&gt;A better solution is runtime feature-gating. The idea of feature-gating is basically to put every big new change behind a toggle that you can control remotely from a server under your control. Whenever you make a change in your mobile app code, you can add a flag for it, which controls whether or not to use the new code.&lt;/p&gt;

&lt;p&gt;Big companies generally have robust tooling for this - and it may be tied into a larger experimentation and analytics framework. But the basic concept is very easy to apply in your own DIY app; you don't need a lot of complexity to get most of the value. Let's look at how.&lt;/p&gt;




&lt;p&gt;Let's suppose we've built a Halloween-themed screen for our app, and want to show it only on October 31st. There are a few ways we could achieve this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hard code a date check on the client;&lt;/li&gt;
&lt;li&gt;Try to update the Play Store before and after halloween with new app versions;&lt;/li&gt;
&lt;li&gt;Use a runtime feature gate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Option 1 is alright, but if something goes wrong - faulty date logic - we'll be celebrating Halloween longer than we wanted.&lt;/p&gt;

&lt;p&gt;Option 2 assumes that our timing with the Play Store will work out perfectly, and also creates a lot of operational churn.&lt;/p&gt;

&lt;p&gt;Option 3 looks really appealing. When Halloween comes, all we have to do is update a file on our backend (or saved to any public location like GitHub Pages, even.) If something goes wrong, we just update the file and no one knows any wiser. Either way, we can update the file at the end of the holiday.&lt;/p&gt;




&lt;p&gt;To gate this change on the client with a runtime feature gate, let's take the following approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a flat JSON file and make it available for download somewhere;&lt;/li&gt;
&lt;li&gt;When the client starts up, go download this file and check its contents;&lt;/li&gt;
&lt;li&gt;Check the feature enablement state before showing the user the Halloween-specific feature(s).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A simple incantation of this JSON file would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"halloween_screen"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, on the client, let's whip together a little network client with Retrofit and KotlinX Serialization. This code will download this file and parse it into a data type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;RuntimeFeaturesService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"features.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;features&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Features&lt;/span&gt;

  &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;RuntimeFeaturesService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://raw.github.com/jameson/proj/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConverterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nc"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asConverterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RuntimeFeaturesService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@Serializable&lt;/span&gt; 
  &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;FeatureList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Feature&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Serializable&lt;/span&gt; 
    &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, you'll probably want to layer on some caching and a repository on top of this. But you can start using the new check immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;halloweenFeaturesEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
  &lt;span class="n"&gt;runtimeFeaturesService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;features&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"halloween_screen"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firstOrNull&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;halloweenFeaturesEnabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pumpkinImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visibility&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VISIBLE&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pumpkinImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visibility&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GONE&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it's time to get rid of the pumpkin image, just update the JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"halloween_screen"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The example above is intentionally simplistic, and uses minimal third-party tooling to achieve its goals. You may quickly want to pivot towards a more robust solution like &lt;a href="https://firebase.google.com/docs/remote-config/#android"&gt;Firebase Remote Config&lt;/a&gt;. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Fakes &amp; Mocks on Android: Well Partner, that depends.</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Sat, 01 Oct 2022 18:56:26 +0000</pubDate>
      <link>https://dev.to/jameson/fakes-mocks-on-android-well-partner-that-depends-h6n</link>
      <guid>https://dev.to/jameson/fakes-mocks-on-android-well-partner-that-depends-h6n</guid>
      <description>&lt;p&gt;Over the course of 2021 and 2022, software testing fashion has swayed away from mocks, towards fakes. So when should you use one or the other? As with most serious engineering questions, the answer is "it depends."&lt;/p&gt;

&lt;p&gt;Yet, in recent times mocks have reached levels of hate not seen since Disco Demolition Night, when America gave up on disco and burned all of their records.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1576231597304516608-906" src="https://platform.twitter.com/embed/Tweet.html?id=1576231597304516608"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1576231597304516608-906');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1576231597304516608&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8bmyguwaigoc0de6po2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8bmyguwaigoc0de6po2.jpg" alt="An image of Disco Demolition Night, showing people in the 1970s burning Disco records. The pile of records has been labeled mocks."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1507416343796191236-550" src="https://platform.twitter.com/embed/Tweet.html?id=1507416343796191236"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1507416343796191236-550');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1507416343796191236&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Even our cultural guru and spiritual leader, the fabled Jake Wharton (🙌), shares this world view:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1104012876048752640-705" src="https://platform.twitter.com/embed/Tweet.html?id=1104012876048752640"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1104012876048752640-705');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1104012876048752640&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://developer.android.com/training/testing/fundamentals/test-doubles" rel="noopener noreferrer"&gt;newer piece of Android documentation&lt;/a&gt; now stops short of wholly recommending fakes, but does only showcase how fakes work - not mocks.&lt;/p&gt;




&lt;h2&gt;
  
  
  An Appeal to the Classics
&lt;/h2&gt;

&lt;p&gt;The canonical essay on test doubles is Martin Fowler's famous 2007 essay, &lt;a href="https://martinfowler.com/articles/mocksArentStubs.html" rel="noopener noreferrer"&gt;Mocks aren't Stubs&lt;/a&gt;. If you haven't ever read it, close this tab, and go read that instead. You'll learn more, and I won't be offended. It offers now-standard definitions for the different types of test doubles, including fakes and mocks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).&lt;/p&gt;

&lt;p&gt;Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of my favorite parts of this article is where Martin explores the two schools of thought, purists and mockists - or the "Detroit" style vs. the "London" style. This is the most correct framing of the debate, as one between schools of thought.&lt;/p&gt;

&lt;p&gt;In full disclosure, I am a purist of the Detroit school. I don't care what behaviors a component need exercise; I prefer to look at its results. Even so, I will continue arguing a place for mocks, ✊.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Sociological Trends Work
&lt;/h2&gt;

&lt;p&gt;Before we get into the merits, I'd like to point out that we are indeed talking about a sociological fad more so than a technical evolution. I'm not pointing this out to be contrarian or diminutive, rather so that we can move forward with appropriate and metered expectations.&lt;/p&gt;

&lt;p&gt;The Gartner Hype Cycle describes how societies respond to new technologies. New tech comes in, people don't yet see or understand the rough edges, and so hopes are high. "Wow, this has benefits!" Later, the drawbacks are learned. The cycle ends with a tool that you can use, but "it depends" when.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  "Mocks Require Mock-able Code"
&lt;/h2&gt;

&lt;p&gt;One argument I've seen is that you have to write impaired source code to use mocks. Actually, I think the opposite is sort of the case.&lt;/p&gt;

&lt;p&gt;In languages that close implementations by default (our beloved Kotlin) you have to create an interface to take advantage of a fake. So, unless you're only doing component-level testing, you'd need to create interfaces for every unit. You'll end up with a lot of silly little interfaces, all in the name of faking.&lt;/p&gt;

&lt;p&gt;On the other hand, maybe you &lt;em&gt;should&lt;/em&gt; just do component-level testing. Here, I define "Component" as a collection of units (classes, interfaces, data types), that serve some well-bounded purpose. For example, a repository dressed with its production dependencies might be a component. Does every little detail beneath the repository need an interface? No, probably not.&lt;/p&gt;

&lt;p&gt;Components are likely the biggest granule that can be reasonably tested on your host's JVM, without needing real implementations on your target device. This is, in my view, the best combination of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"Right-sized" abstractions with interfaces;&lt;/li&gt;
&lt;li&gt;Bang-for-your-buck w.r.t. test coverage;&lt;/li&gt;
&lt;li&gt;Most approximating of real world behavior, while still being able to run on your workstation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But, how would it work with mocks? You don't need to do anything to use mocks, that's arguably part of their charm. They're a great choice for legacy code which has few walls of abstraction. Frameworks like Mockito and MockK will happily spoof final/closed functionalities - even returning null from non-null functions. This is thanks to the Magic of Reflection™. Are all of these good traits? I mean: probably not. However, it is still true that mocks are less impacting to your actual source code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reflection is Slow and Spooky
&lt;/h2&gt;

&lt;p&gt;Bars, dude. Bars. This is true.&lt;/p&gt;

&lt;p&gt;Even Java, upon introducing the capability to its own language remarked in its documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, it is possible to create a fake that uses reflection (your fake implementation could internally use a dynamic proxy pattern, or something.) And, it is technically possible to create mocks that don't use reflection. You could write a custom, concrete implementation for every single test case - obviously, though, that would be just: wow.&lt;/p&gt;

&lt;p&gt;Never-the-less, it is most often the case that mocks = reflection, fakes = compiled contracts. So, fakes &lt;em&gt;do&lt;/em&gt; have the usual benefits of a reflection-free existence here: they run faster, and you can catch more bugs in your test implementation through the compiler instead of while running your test.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fakes Have an Upfront Cost
&lt;/h2&gt;

&lt;p&gt;Fakes may require more up-front investment. Let's suppose you have a happy little interface like this:&lt;/p&gt;

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

&lt;p&gt;Now, you want to use it, say in some use case - maybe you're updating user preferences.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpdateUserPreferencesUseCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;disableAnimations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... &lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And, let's further suppose that your code layout looks something like this:&lt;/p&gt;

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

&lt;p&gt;When you go to write this &lt;code&gt;UpdateUserPreferencesUseCaseTest&lt;/code&gt;, you come upon a decision point. Should you create a fake of the &lt;code&gt;UserRepository&lt;/code&gt; or should you mock it? And where should that code live, in either case?&lt;/p&gt;

&lt;p&gt;For small interfaces, the cost of writing a fake vs. a few mock statements is pretty similar. You can write a fake of two functions very quickly - in about as much time as it takes to write the two mocking clauses.&lt;/p&gt;

&lt;p&gt;For larger interfaces, the cost is higher right now. It will take you longer to think through a reasonable fake implementation for 5, 10, 20 function contracts. It will be cheaper to write only the two mock statements your test needs to pass, right now.&lt;/p&gt;

&lt;p&gt;So in either case, the fake will take &lt;em&gt;at least&lt;/em&gt; as much energy as the mock for right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  "Fakes Have Lower Maintenance Cost"
&lt;/h2&gt;

&lt;p&gt;Now, the &lt;em&gt;real&lt;/em&gt; argument we hear in favor of fakes is that they have lower long-term maintenance cost. But is that true? Long-term maintenance cost &lt;em&gt;also&lt;/em&gt; depends on various factors.&lt;/p&gt;

&lt;p&gt;Suppose you put your fake in &lt;code&gt;:settings:impl&lt;/code&gt;, next to your &lt;code&gt;UpdateUserPreferencesUseCaseTest&lt;/code&gt;. At this point, there's no long-term cost. The one fake will have the same maintenance cost as the one file using mocks.&lt;/p&gt;

&lt;p&gt;But what happens if you have 5 or more uses of that &lt;code&gt;UserRepository&lt;/code&gt; in tests across your code base? Well, the natural evolution would be to end up with 5 different fake implementations, each living nearby to the tests that need them, in different modules. If you end up here, fakes are in fact awful for long-term maintenance.&lt;/p&gt;

&lt;p&gt;Of course, there's a better way. A better strategy would have been to put the fake implementation in some re-usable module, say &lt;code&gt;:users:fakes&lt;/code&gt;:&lt;/p&gt;

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

&lt;p&gt;If you do this, all of your tests that need a &lt;code&gt;FakeUserRepository&lt;/code&gt; can use the one common implementation. As the number of tests that need this component grow, so will you see improved long-term maintenance costs, and amortized returns on your up-front investment to build it. That's essentially Zac's argument in this graph:&lt;/p&gt;

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

&lt;p&gt;But, here's the thing. This has nothing to do with fakes. You can do this with mocks, too! You could create a mocks factory and put it in &lt;code&gt;:users:mocks&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;UserRepositoryMocking&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mockk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// etc.&lt;/span&gt;
    &lt;span class="nf"&gt;every&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Joe Swanson"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;So, it's more how you re-use code that impacts long-term maintenance cost, not something intrinsic about fakes vs. mocks.&lt;/p&gt;

&lt;p&gt;If you put your fake implementations next to individual tests, you'll end up having to create multiple fake implementations, which will be costly to maintain.&lt;/p&gt;

&lt;p&gt;And vice-versa, if you don't put your mocking code in some central tool, you'll end up with a lot of repeated setup code that you'll have to implement in every test.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Honestly, one of the things I dislike most about tech articles is when someone just leaves you hanging with "it depends", and thinks they've done something clever with that. Like bruv, we pay you proper quid to get this stuff sorted, innit? &lt;/p&gt;

&lt;p&gt;Here are the specific best practices that I think you should bring into your code base.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For interfaces that will see high re-use, write a fake implementation. Place the fake into a re-usable test module so that future tests can leverage it, too.&lt;/li&gt;
&lt;li&gt;For larger interfaces, legacy components without interfaces, or components with very few uses ("the long tail"), prefer mocks. This will have lower up-front and lower long-term maintenance cost.&lt;/li&gt;
&lt;li&gt;For mocking behavior you use across many tests, create a re-usable factory utility to centralize boilerplate, akin to what you do for fakes.&lt;/li&gt;
&lt;li&gt;When authoring new source code, honor the Single Responsibility Principle and Interface Segregation Principle, which will facilitate either of these approaches, as you go forward.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Happy testing 🎉&lt;/p&gt;

</description>
      <category>testing</category>
      <category>android</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Bash Completion for Git on Mac OS X Monterey</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Wed, 19 Jan 2022 07:23:34 +0000</pubDate>
      <link>https://dev.to/jameson/bash-completion-for-git-on-mac-os-x-monterrey-3imd</link>
      <guid>https://dev.to/jameson/bash-completion-for-git-on-mac-os-x-monterrey-3imd</guid>
      <description>&lt;p&gt;For years now, I've had a working recipe to enable bash autocompletion for git on my Mac. Recently that broke due to a packaging change with Xcode's CommandLineTools. So, let's take a moment to review how this all works, and how we can get it working (again).&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick background on bashrc files
&lt;/h2&gt;

&lt;p&gt;bashrc files are environment configuration scripts that Bash includes when it starts. You can use them to customize your CLI experience.&lt;/p&gt;

&lt;p&gt;However, the single &lt;code&gt;~/.bashrc&lt;/code&gt; file can get unmanageable quickly. So just like any other piece of software, we'd like to template things and break the problem into smaller chunks.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;dotfiles&lt;/code&gt; GitHub repo includes the collection of &lt;code&gt;.bashrc&lt;/code&gt; template files I use on a day-to-day basis:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jamesonwilliams" rel="noopener noreferrer"&gt;
        jamesonwilliams
      &lt;/a&gt; / &lt;a href="https://github.com/jamesonwilliams/dotfiles" rel="noopener noreferrer"&gt;
        dotfiles
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Jameson's . (dot) files for Linux
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;dotfiles&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Jameson's . (dot) files for UNIX and Linux family command-line
environments.&lt;/p&gt;
&lt;p&gt;One goal of these dot files is to converge on a similar experience
across some different UNIX/Linux systems that we encounter in the wild:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ubuntu&lt;/li&gt;
&lt;li&gt;Debian&lt;/li&gt;
&lt;li&gt;RHEL&lt;/li&gt;
&lt;li&gt;Amazon Linux&lt;/li&gt;
&lt;li&gt;Mac OS X&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;To install the dot files for a particular user on your system, do:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/jamesonwilliams/dotfiles.git
./dotfiles/install.sh&lt;/pre&gt;

&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jamesonwilliams/dotfiles" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Basically, they work as follows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;~/.bash_profile&lt;/code&gt; is the hook used by Mac OS X to start loading your Bash customizations. Mine does nothing other than call &lt;code&gt;~/.bashrc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;My &lt;code&gt;~/.bashrc&lt;/code&gt; itself contains no real logic, it just sources a bunch of template files that handle specific sub-problems.&lt;/li&gt;
&lt;li&gt;One of these template files is called &lt;code&gt;~/.bashrc.darwin&lt;/code&gt;, and it includes some fixes for Mac-specific quirks. Stuff that's generally useful on Linux or other UNIX flavors &lt;em&gt;doesn't&lt;/em&gt; go into this file.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How it used to work on Mac OS X
&lt;/h2&gt;

&lt;p&gt;One of the quirks of using OS X is that tab completion with git doesn't work out of the box. So for years, I've had a little bit of configuration to handle this case:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# In ~/.bashrc.darwin&lt;/span&gt;

&lt;span class="nv"&gt;cli_tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'/Library/Developer/CommandLineTools'&lt;/span&gt;
&lt;span class="nv"&gt;git_core&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cli_tools&lt;/span&gt;&lt;span class="s2"&gt;/usr/share/git-core"&lt;/span&gt;
&lt;span class="nv"&gt;git_completion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$git_core&lt;/span&gt;&lt;span class="s2"&gt;/git-completion.bash"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;which git&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$git_completion&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$git_completion&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;If git is installed and the git completion file can be found, we source it into the environment.&lt;/p&gt;

&lt;h1&gt;
  
  
  How it works now
&lt;/h1&gt;

&lt;p&gt;Recently, that &lt;code&gt;git-completion.bash&lt;/code&gt; file disappeared from the CommandLineTools install, and so my tab completion stopped working.&lt;/p&gt;

&lt;p&gt;Turns out it now lives here:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;

/Applications/Xcode.app/Contents/Developer/usr/share/git-core/git-completion.bash


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

&lt;/div&gt;

&lt;p&gt;So all that's needed is to update our pathing for &lt;code&gt;git_core&lt;/code&gt; in &lt;code&gt;~/.bashrc.darwin&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# In ~/.bashrc.darwin&lt;/span&gt;

&lt;span class="nv"&gt;xcode_dev_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'/Applications/Xcode.app/Contents/Developer'&lt;/span&gt;
&lt;span class="nv"&gt;git_core&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$xcode_dev_dir&lt;/span&gt;&lt;span class="s2"&gt;/usr/share/git-core"&lt;/span&gt;
&lt;span class="nv"&gt;git_completion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$git_core&lt;/span&gt;&lt;span class="s2"&gt;/git-completion.bash"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;which git&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$git_completion&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$git_completion&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Save the file, reboot your laptop, and profit. Happy git-ing.&lt;/p&gt;

</description>
      <category>git</category>
      <category>bash</category>
      <category>macosx</category>
      <category>cli</category>
    </item>
    <item>
      <title>Scaling Development of an Android App</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Tue, 14 Sep 2021 03:31:14 +0000</pubDate>
      <link>https://dev.to/jameson/scaling-development-of-an-android-app-2fl4</link>
      <guid>https://dev.to/jameson/scaling-development-of-an-android-app-2fl4</guid>
      <description>&lt;p&gt;This article explores different strategies for organizing and packaging Android source code as your organization grows. We also look at ways to divide up ownership across different developers in your organization. We’ll follow a typical growth trajectory of an Android app, starting from a simple toy project and ending up with a large consumer-facing product.&lt;/p&gt;

&lt;h1&gt;
  
  
  Act 1: The Simple Sample App
&lt;/h1&gt;

&lt;p&gt;When you’re working on a small app by yourself, your project structure likely looks something like a &lt;a href="https://en.wikipedia.org/wiki/Matryoshka_doll"&gt;&lt;em&gt;Matryoshka&lt;/em&gt;&lt;/a&gt; (“Russian-nesting”) doll. At the outermost layer, you’ve got a single Git repo. The repo contains a single root Android Gradle project. In the Gradle project, you’ve got a single Android application module called "app.” Inside the app module, you might have a few small &lt;a href="https://kotlinlang.org/docs/packages.html"&gt;Kotlin packages&lt;/a&gt; into which you’ve &lt;a href="https://developer.android.com/jetpack/guide#common-principles"&gt;separated&lt;/a&gt; your presentation logic from your domain modeling code. Lastly, each Kotlin package contains a few files/classes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6yrsn6okuub4owtbqxj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6yrsn6okuub4owtbqxj.png" alt="Structure of a basic Android project" width="592" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Right now, you're the only person working on this codebase, and you have access to everything. When it comes time to prepare an &lt;a href="https://en.wikipedia.org/wiki/Android_application_package"&gt;apk&lt;/a&gt; for installation, there are no intermediate steps. All of the files are compiled and &lt;a href="https://developer.android.com/studio/command-line/d8"&gt;indexed&lt;/a&gt; in one big bang.&lt;/p&gt;

&lt;h1&gt;
  
  
  Act 2: The Prototype That “Made It”
&lt;/h1&gt;

&lt;p&gt;Good news - the business has decided to move forward with additional investment in your little Android app. As you build out more features, you’re going to add more Kotlin files and more Kotlin packages. Separating your code into small files and packages will encourage clean &lt;a href="https://en.wikipedia.org/wiki/Single-responsibility_principle"&gt;single-responsibility&lt;/a&gt; components, and self-document the relationships between them. At this phase, your project might have a hierarchy of Kotlin packages and dozens of Kotlin files/classes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5gyrzrw1rf7r7h2yrvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5gyrzrw1rf7r7h2yrvj.png" alt="Structure of an early-stage app that is growing" width="592" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Act 3: A Real Production App
&lt;/h1&gt;

&lt;p&gt;After a while of building features for the business, you’ve got a "real" Android app on your hands. A few key changes occur at this phase.&lt;/p&gt;

&lt;p&gt;Firstly, you might have additional developers working with you on the same codebase, now. And for now, that isn’t too much of a problem. There are only 2-5 of you, and you’re staying in close communication about design and implementation. You review each other’s code and you rely on &lt;a href="https://agilemanifesto.org/"&gt;“individuals and interactions over process.”&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the new investment, you’re getting lots done -- and your single codebase might be starting to get unwieldy. Perhaps the app module’s &lt;code&gt;build.gradle&lt;/code&gt; is getting longer and more difficult to keep organized. You’re probably also thinking about which of your components can be re-used and which are the best ways to do it. Maybe some of those utilities are even generic enough that they could be useful &lt;em&gt;beyond your immediate project&lt;/em&gt;. You’ve got some decisions to make.&lt;/p&gt;

&lt;p&gt;Firstly, should you split the utility code out into a new library module? Generally speaking, this is a good idea if you’ve got a well-designed public contract that’s being exercised by 3 or more other components. It’s also a pretty minor change to make, and with little downside. The primary gain here will be to simplify your build script and to create a more explicit barrier between groups of interacting components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpt3o03tyi31xr6f5yscf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpt3o03tyi31xr6f5yscf.png" alt="Structure of an Android app with several Gradle modules" width="592" height="422"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Creating re-usable library modules will also help reduce your build times, since Gradle will be able to recycle any already-built modules that haven’t been touched. There are a number of steps needed to produce an apk, but re-usable library modules can (at minimum) chip away at the incremental compilation work that’s needed to construct the final apk.&lt;/p&gt;

&lt;p&gt;With a bonafide library module in your project, you’ve unlocked an additional capability. You can now release your library module as a public &lt;a href="https://developer.android.com/studio/projects/android-library#CreateLibrary"&gt;Android ARchive&lt;/a&gt;. Android libraries such as OkHttp or Glide are released this way, and are distributed via the &lt;a href="https://maven.apache.org/repository/"&gt;Maven Central Repository&lt;/a&gt; (or &lt;a href="https://maven.google.com/web/index.html"&gt;Google’s mirror of it&lt;/a&gt;.) And just as is the case with OkHttp or Glide, your app can depend on a &lt;strong&gt;&lt;em&gt;remote&lt;/em&gt;&lt;/strong&gt; copy of your prebuilt library instead of directly on the local module code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5yfqmswpxhvw1ypqf7aj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5yfqmswpxhvw1ypqf7aj.png" alt="Interaction between application and library modules with an external artifact repository" width="622" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a few benefits to doing this. Even with a local library project, you have to do at least one initial build to create the local library artifact. Not so with an external library. Now, you just pull a built artifact and skip that entire compilation unit.&lt;/p&gt;

&lt;p&gt;By the time you’re publishing a library artifact and your app depends on it as an external artifact, there’s little benefit to keeping it in the same Gradle project. So, you can further simplify things by splitting the repo into two Gradle projects entirely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuocke0edkb5duq9rn413.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuocke0edkb5duq9rn413.png" alt="Structure of an Android repo with two Gradle projects" width="632" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allows you to operate on a smaller application project, skipping some compilation, and with some code out-of-sight and out-of-mind.&lt;/p&gt;

&lt;h1&gt;
  
  
  Act 4: An Enterprise Monorepo
&lt;/h1&gt;

&lt;p&gt;Few organizations ever grow beyond “Act 3.” But there are several additional challenges that creep up, for those that do. Your team might have some of the problems below -- or perhaps you already have all of them.&lt;/p&gt;

&lt;p&gt;Firstly, an "enterprise monorepo" is no longer the collective effort of just a few developers. Enterprise monorepos may contain hundreds of thousands of lines of code -- sometimes millions of lines of code. You might have dozens of Engineers working in different parts of the company, all trouncing on the same repo. Each engineer brings different priorities, perspectives, and proficiencies. You’ll have multiple managers leading different teams, reporting out delivery schedules.&lt;/p&gt;

&lt;p&gt;With more code and more developers, the obstacles at this phase are both technical and social. One central questions becomes “how do we break apart the codebase so that different teams can function independently?”&lt;/p&gt;

&lt;p&gt;You need to start creating tooling to assign ownership. One solution is to use a &lt;a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners"&gt;&lt;code&gt;CODEOWNERS&lt;/code&gt;&lt;/a&gt; file, to enforce required approvals when different parts of the repository are touched.&lt;/p&gt;

&lt;p&gt;Even more-so than when the app was small, your build times are probably getting bad. It becomes technically-complex to reduce the footprint of the top-level application module, and feature teams may not be incentivized to invest into that effort.&lt;/p&gt;

&lt;p&gt;At this point, there are two key technical strategies you can employ to mitigate this.&lt;/p&gt;

&lt;p&gt;Firstly, an authoritative voice must decree that all feature teams shall own and publish prebuilt, strongly-versioned library modules. In some-senses, this is moving your organization towards a &lt;em&gt;Mobile-Service-Oriented-Architecture&lt;/em&gt; of sorts. (The key difference being that services talk over HTTP, whereas your libraries talk across compilation units.) This strategy yields three key benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The feature library may be consumed and developed by a team-specific top-level application -- a lightweight “dev app" or “sample app." This lightweight app will have limited functionality, and be much easier to work with than the main app itself.&lt;/li&gt;
&lt;li&gt;The feature library may be consumed by a different sort of top-level actor: an integration test harness. This will provide a new integration-test entry point.&lt;/li&gt;
&lt;li&gt;When the main application consumes the feature library, it will not incur the cost of recompilation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As with SOA, the details of “how” a library contract is fulfilled is left to the responsible team to determine.&lt;/p&gt;

&lt;p&gt;The second approach you can utilize to mitigate app-creep is to invest in an &lt;em&gt;extensibility programme&lt;/em&gt;. Namely, you need to develop an application architecture that enables feature teams to &lt;em&gt;build outside of&lt;/em&gt; the existing application project.&lt;/p&gt;

&lt;h1&gt;
  
  
  Act 5: Executing an Extensibility Programme
&lt;/h1&gt;

&lt;p&gt;In order for a feature to live entirely outside of the app module, it needs to be able to call the various dependencies it needs to function -- &lt;em&gt;none of which can be in the app module&lt;/em&gt;. In fact, it is impossible for the library module to call back into the app directly: this would create a cyclical dependency.&lt;/p&gt;

&lt;p&gt;The first phase of your extensibility program should be to identify the &lt;em&gt;“big lists of things”&lt;/em&gt; files. Every app has them. They often materialize as big lists of constants - a routing table, perhaps. The problem with these types of files is that a feature module will necessarily need to integrate with them -- and so will necessarily need to have some degree of footprint in the app.&lt;/p&gt;

&lt;p&gt;Such files are usually designed without &lt;a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle"&gt;the open-closed principle&lt;/a&gt; in mind. (The author wasn't naive, they were just designing a smaller-sized app at that point. And in that context and time, they actually did the &lt;em&gt;right&lt;/em&gt; thing.)&lt;/p&gt;

&lt;p&gt;You can often rework such components by either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Code-generating them. To do this, create build tool that sources input fragments from multiple source repositories.&lt;/li&gt;
&lt;li&gt;Adding run-time pluggability. To do so, make an interface for feature components so that they can &lt;code&gt;addPlugin()&lt;/code&gt; and &lt;code&gt;removePlugin()&lt;/code&gt; (or &lt;code&gt;addRoute()&lt;/code&gt;, &lt;code&gt;removeRoute()&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The other key strategy is to use &lt;a href="https://en.wikipedia.org/wiki/Inversion_of_control"&gt;&lt;em&gt;Inversion of Control (IoC)&lt;/em&gt;&lt;/a&gt;. IoC became popular in large service monoliths. And guess what? We necessarily are building a monolith! At the end of all of this, there's only one app going to the Play Store. IoC dependency containers enable components to speak to one another over abstractions (interfaces), without knowing the details of how things actually will be carried out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca2aa7w7tu7x0wxc4w0c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca2aa7w7tu7x0wxc4w0c.png" alt="Structure of an Android application that uses an IoC container to separate contract and implementation libraries" width="582" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the diagram above, the fictitious “Pizza domain” library needs to use the View/UI contacts so that it can initiate the display of some UI screen. To do so, the Pizza domain takes a dependency on the View/UI contract library. When the application module pulls in its various dependencies, it assigns a concrete implementation of the view/UI contract via &lt;em&gt;service binding&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Each vertical feature (in yellow) publishes a contract library and an implementation library. The application module itself uses a &lt;a href="https://en.wikipedia.org/wiki/Service_locator_pattern"&gt;Service Locator&lt;/a&gt; to create a binding between contract interfaces and concrete implementations there-of. Ordinarily, this binding code is owned by a supervisory team that actually releases the app, so they can police the list and enforce social policies over it during code review. (In the example above, the binding code lives in the app itself, but could be further isolated into an independent library.)&lt;/p&gt;

&lt;h1&gt;
  
  
  Act 6. Further Divisions of Source Code
&lt;/h1&gt;

&lt;p&gt;At this phase, we have numerous independently-versioned libraries being published. We have an extensible application architecture that has helped us to prevent growth in our application module itself. Builds are generally faster, and teams can develop and test their features without needing to use the main application module all the time.&lt;/p&gt;

&lt;p&gt;By now, you still have lots of thrash on your monorepo. You’re still using &lt;code&gt;CODEOWNERS&lt;/code&gt; to help with change control. Should you continue dividing the project further, into separate Git repos? That would surely lead to one of the most decoupled and distributed codebases achievable, right?&lt;/p&gt;

&lt;p&gt;There’s overhead in setting up all of these independent repositories, yea, but you could potentially script their creation with some Infrastructure-as-Code-type-tooling. After a while, many of these repositories will be low-touch. Out-of-sight, out-of-mind. Stable APIs will form, and new changes will cease to come in.&lt;/p&gt;

&lt;p&gt;The more repositories, Gradle projects, and Gradle modules you create, the more overhead there will be to get work done. But, your teams &lt;em&gt;will&lt;/em&gt; be fully out of each other’s way. For the most part.&lt;/p&gt;

&lt;h1&gt;
  
  
  Handling Dependency Updates
&lt;/h1&gt;

&lt;p&gt;As your application proceeds through these phases of life, you’ll find that managing dependency upgrades will be a pain. When everything is all in one place, you could rely on Herculean efforts to upgrade the single code base. One all-nighter, one Engineer, block out some time on everyone’s calendar, and get the Pull Request in.&lt;/p&gt;

&lt;p&gt;That's less palpable as your organization grows bigger. You need additional social mechanisms to plan for changes, and you won’t be able to make them as quickly.&lt;/p&gt;

&lt;p&gt;One workflow to execute dependency upgrades across teams might be to broadcast "need by" dates, and ask teams to provide the version numbers of their libraries that will institute the upgrade. Then, the supervisory team (app release team) can create a single PR which updates the dependency versions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing Thoughts
&lt;/h1&gt;

&lt;p&gt;As your app and team gets bigger, there is generally more overhead required to add more structure to it. Don't use a Jackhammer on drywall, and don't use a putty knife on concrete. Only decompose your application when you hit the next incremental limit of scale.&lt;/p&gt;

&lt;p&gt;Happy architecting.&lt;/p&gt;

</description>
      <category>android</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Unboxing the Qualcomm RB5 Robotics Kit</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Mon, 19 Apr 2021 00:49:49 +0000</pubDate>
      <link>https://dev.to/jameson/unboxing-the-qualcomm-rb5-robotics-kit-4a0l</link>
      <guid>https://dev.to/jameson/unboxing-the-qualcomm-rb5-robotics-kit-4a0l</guid>
      <description>&lt;p&gt;Today I'll be showing some of my very first interactions with a &lt;a href="https://developer.qualcomm.com/qualcomm-robotics-rb5-kit"&gt;Qualcomm RB5 Robotics Kit&lt;/a&gt;. In their words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Qualcomm Robotics RB5 development kit combines the promise of 5G with the computing power for AI, deep learning, computer vision, 7-camera concurrency, rich multimedia and security&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Inside the box, we get three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The board itself,&lt;/li&gt;
&lt;li&gt;A USB-C cable,&lt;/li&gt;
&lt;li&gt;A power brick.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F380130wyh4vl598jy2xw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F380130wyh4vl598jy2xw.jpeg" alt="Inside the box, there's the RB5 main board, a USB-C cable, and a power brick" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1dpr0bhrt6qb9ru2ae4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1dpr0bhrt6qb9ru2ae4.jpeg" alt="RB5 main board, USB-C cable, and a power brick" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.qualcomm.com/qualcomm-robotics-rb5-kit/quick-start-guide/qualcomm_robotics_rb5_development_kit_bring_up"&gt;bringup guide&lt;/a&gt; tells us we'll need a Linux workstation, and a few utilities from the &lt;em&gt;Android&lt;/em&gt; SDK: &lt;code&gt;adb&lt;/code&gt; &amp;amp; &lt;code&gt;fastboot&lt;/code&gt;. In short, it looks like a familiar programming model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build device images on your local machine;&lt;/li&gt;
&lt;li&gt;Deploy and run those images on the device.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'm on a Mac right now, so I won't have access to the full toolset needed to prepare the images. However, I've got a working &lt;code&gt;adb&lt;/code&gt; and &lt;code&gt;fastboot&lt;/code&gt;, so can easily interact with the device. This will likely &lt;em&gt;continue&lt;/em&gt; to be my workflow, even after I get a Linux build host going in EC2.&lt;/p&gt;

&lt;p&gt;Let's get the kit running. I connect the power brick, and then I connect the USB-C cable into my MacBook. After a bit, a green LED indicator lights up on the device:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgfzdq9nqtaah2jzapc6y.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgfzdq9nqtaah2jzapc6y.jpeg" alt="RB5 kit plugged in and powered up" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, I'm able to see the device via &lt;code&gt;adb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;adb devices
&lt;span class="go"&gt;List of devices attached
ZTR10S1600CS    device
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can even login and probe for some details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;adb shell
&lt;span class="gp"&gt;sh-4.4#&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The device is running Ubuntu 18.04.05:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;sh-4.4#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;lsb_release &lt;span class="nt"&gt;-a&lt;/span&gt; 
&lt;span class="go"&gt;No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.5 LTS
Release:    18.04
Codename:   bionic
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The processor has 8 cores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;sh-4.4#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt; 
&lt;span class="go"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's an &lt;a href="https://en.wikipedia.org/wiki/AArch64"&gt;&lt;code&gt;AArch64&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;sh-4.4#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cpuinfo | &lt;span class="nb"&gt;grep&lt;/span&gt; ^Processor 
&lt;span class="go"&gt;Processor   : AArch64 Processor rev 14 (aarch64)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;sh-4.4#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; 
&lt;span class="gp"&gt;Linux qrb5165-rb5 4.19.125 #&lt;/span&gt;1 SMP PREEMPT Sat Mar 20 11:48:10 CST 2021 aarch64 aarch64 aarch64 GNU/Linux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The board has 7650MB of memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;sh-4.4#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;free &lt;span class="nt"&gt;-m&lt;/span&gt;
&lt;span class="go"&gt;              total        used        free      shared  buff/cache   available
Mem:           7650        1112        6058          12         478        6848
Swap:          3825           0        3825
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few USB devices on the board, an &lt;a href="https://www.digchip.com/datasheets/3291810-ax88179-usb-3-0-to-10-100-1000m.html"&gt;AX88179&lt;/a&gt; ethernet controller, and a couple of USB hubs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;sh-4.4#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;lsusb 
&lt;span class="go"&gt;Bus 002 Device 003: ID 0b95:1790 ASIX Electronics Corp. AX88179 Gigabit Ethernet
Bus 002 Device 002: ID 05e3:0625 Genesys Logic, Inc. 
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 002: ID 05e3:0610 Genesys Logic, Inc. 4-port hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not much on the PCI bus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;sh-4.4#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;lspci 
&lt;span class="go"&gt;00:00.0 PCI bridge: Qualcomm Device 010b (rev ff)
01:00.0 Unassigned class [ff00]: Qualcomm Device 1101 (rev ff)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lots and lots of partitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;sh-4.4#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mount 
&lt;span class="go"&gt;/dev/sda7 on / type ext4 (rw,relatime)
devtmpfs on /dev type devtmpfs (rw,relatime,size=2671320k,nr_inodes=667830,mode=755)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755)
tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/schedtune type cgroup (rw,nosuid,nodev,noexec,relatime,schedtune)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/debug type cgroup (rw,nosuid,nodev,noexec,relatime,debug)
mqueue on /dev/mqueue type mqueue (rw,relatime)
debugfs on /sys/kernel/debug type debugfs (rw,relatime)
tmpfs on /var/volatile type tmpfs (rw,relatime)
configfs on /sys/kernel/config type configfs (rw,relatime)
fusectl on /sys/fs/fuse/connections type fusectl (rw,relatime)
/dev/sde9 on /dsp type ext4 (ro,nosuid,nodev,noexec,noatime,discard,noauto_da_alloc,data=ordered)
/dev/sde4 on /firmware type vfat (ro,nodev,noexec,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro)
/dev/sde5 on /bt_firmware type vfat (ro,nodev,noexec,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro)
adb on /dev/usb-ffs/adb type functionfs (rw,relatime)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks like a 128GB storage chip, split apart into different partitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;sh-4.4#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="go"&gt;Filesystem      Size  Used Avail Use% Mounted on
/dev/root        99G  6.0G   93G   7% /
devtmpfs        2.6G     0  2.6G   0% /dev
tmpfs           3.8G     0  3.8G   0% /dev/shm
tmpfs           3.8G   12M  3.8G   1% /run
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           3.8G     0  3.8G   0% /sys/fs/cgroup
tmpfs           3.8G     0  3.8G   0% /var/volatile
/dev/sde9        59M   24M   34M  42% /dsp
/dev/sde4       395M   61M  335M  16% /firmware
/dev/sde5        64M  368K   64M   1% /bt_firmware
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've found &lt;em&gt;some&lt;/em&gt; of the components in the &lt;a href="https://developer.qualcomm.com/qualcomm-robotics-rb5-kit/hardware-reference-guide"&gt;hardware spec sheet&lt;/a&gt; so far, but just a few. Of particular interest, I'd like to locate some of the coprocessors (the "heterogeneous computing" bit):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Qualcomm Adreno 650 GPU&lt;/li&gt;
&lt;li&gt;Qualcomm Hexagon DSP&lt;/li&gt;
&lt;li&gt;Qualcomm Spectra 480 image processing&lt;/li&gt;
&lt;li&gt;Adreno 665 VPU video encode/decode&lt;/li&gt;
&lt;li&gt;Qualcomm Secure Processing Unit SPU240&lt;/li&gt;
&lt;li&gt;Qualcomm Neural Processing unit NPU230&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In an upcoming article, we'll start trying to exercise some of these components!&lt;/p&gt;

</description>
      <category>qualcomm</category>
      <category>robotics</category>
      <category>hardware</category>
    </item>
    <item>
      <title>Android WebSocket Clients for Amazon API Gateway</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Fri, 08 Jan 2021 09:25:05 +0000</pubDate>
      <link>https://dev.to/jameson/android-websocket-clients-for-amazon-api-gateway-1oa8</link>
      <guid>https://dev.to/jameson/android-websocket-clients-for-amazon-api-gateway-1oa8</guid>
      <description>&lt;p&gt;WebSockets are a great way to achieve bi-directional communication between a mobile app and a backend. In December 2018, Amazon's API Gateway service &lt;a href="https://aws.amazon.com/blogs/compute/announcing-websocket-apis-in-amazon-api-gateway/"&gt;launched serverless support for WebSockets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Setting it all up is somewhat involved. I recommend starting with the &lt;a href="https://github.com/aws-samples/simple-websockets-chat-app"&gt;Simple WebSockets Chat backend&lt;/a&gt; they provide as a demo. We'll focus on how to talk to it from Android, using Square's familiar &lt;a href="https://square.github.io/okhttp/"&gt;OkHttp&lt;/a&gt; or JetBrains' newer &lt;a href="https://ktor.io/docs/clients-index.html"&gt;Ktor&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/"&gt;activate an AWS account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-mac.html#cliv2-mac-install-cmd"&gt;The latest version of the AWS CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/wscat#installation"&gt;&lt;code&gt;wscat&lt;/code&gt;&lt;/a&gt; installed via &lt;code&gt;npm&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-mac.html#serverless-sam-cli-install-mac-sam-cli"&gt;AWS SAM CLI&lt;/a&gt; installed via Homebrew (if on Mac.)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/studio"&gt;Android Studio&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting Up a Backend
&lt;/h2&gt;

&lt;p&gt;Installing the serverless demo app is pretty straight-forward.&lt;/p&gt;

&lt;p&gt;Checkout the Chat app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aws-samples/simple-websockets-chat-app.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And deploy it to your account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a while, that command will finish. You'll be able to see the provisioned resources by inspecting the outputs of the CloudFormation stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation describe-stacks &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--stack-name&lt;/span&gt; simple-websocket-chat-app &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Stacks[].Outputs'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It creates a DynamoDB table, three Lambda functions, an AWS IAM role, and an API Gateway with WebSockets support.&lt;/p&gt;

&lt;p&gt;After you've glanced over the output to understand what all was created, let's zero in on the WebSocket endpoint itself. We'll need to find the URI that our client can use, to access it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation describe-stacks &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--stack-name&lt;/span&gt; simple-websocket-chat-app &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Stacks[].Outputs[]'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.[]|select(.OutputKey=="WebSocketURI").OutputValue'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should output something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;wss://azgu36n0vf.execute-api.us-east-1.amazonaws.com/Prod
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Command Line Testing using &lt;code&gt;wscat&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Let's quickly test the backend, to make sure it works as we expect. We'll use the &lt;a href="https://github.com/websockets/wscat#wscat"&gt;&lt;code&gt;wscat&lt;/code&gt; command-line utility&lt;/a&gt;. The command below will open a long-lived connection to the API Gateway, and we'll be able to send and receive messages in a subshell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wscat &lt;span class="nt"&gt;-c&lt;/span&gt; wss://azgu36n0vf.execute-api.us-east-1.amazonaws.com/Prod
&lt;span class="go"&gt;Connected (press CTRL+C to quit)
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"action"&lt;/span&gt;: &lt;span class="s2"&gt;"sendmessage"&lt;/span&gt;, &lt;span class="s2"&gt;"data"&lt;/span&gt;: &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt; foo
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON format above is required by the app we deployed. You can change the value of &lt;code&gt;"foo"&lt;/code&gt;, but you can't change anything else.&lt;/p&gt;

&lt;p&gt;If you try to pass something else, it doesn't work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Hello, how are you?
&lt;span class="go"&gt;&amp;lt; {"message": "Forbidden", "connectionId":"Y0bEuc0UIAMCIiA=", "requestId":"Y0bwuGjXIAMFmEg="}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, if you open up multiple terminal windows, and connect them all to the endpoint, they'll all receive a valid message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling the API from Android via OkHttp
&lt;/h2&gt;

&lt;p&gt;Now that we know the WebSocket API is working, let's start building an Android app to use as a client, instead.&lt;/p&gt;

&lt;p&gt;WebSockets have been &lt;a href="https://developer.squareup.com/blog/web-sockets-now-shipping-in-okhttp-3-5/"&gt;supported in OkHttp since 3.5&lt;/a&gt;, which came out all the way back in 2016.&lt;/p&gt;

&lt;p&gt;Initializing a WebSocket client is straight-forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"wss://azgu36n0vf.execute-api.us-east-1.amazonaws.com/Prod"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebSocketListener&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Called asynchronously when messages arrive&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newWebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To send a message to the API Gateway, all we have to do is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sendmessage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello from Android!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can add a button to our UI with &lt;a href="https://developer.android.com/topic/libraries/view-binding"&gt;ViewBinding&lt;/a&gt;, and fire the WebSocket message whenever we click it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello from Android!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since all of the threading is handled inside OkHttp, there really isn't a lot more to it. Save a handle to your view binding and to to your WebSocket client when you create your &lt;code&gt;Activity&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ActivityMainBinding&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ui&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ActivityMainBinding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inflate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layoutInflater&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;setContentView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// As above&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Calling the API from Android via Ktor
&lt;/h2&gt;

&lt;p&gt;Ktor is built with the assumption that you're using Coroutines, and managing your own scope/context. This makes it a more flexible tool, but adds some additional complexity.&lt;/p&gt;

&lt;p&gt;The basic setup for the tool is going to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ktor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ktor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encodedPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Access to a WebSocket session&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="cm"&gt;/* not suspend */&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"wss://azgu36n0vf.execute-api.us-east-1.amazonaws.com/Prod"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ktor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OkHttp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebSockets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ktor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the trailing closure, you have access to an instance of &lt;a href="https://api.ktor.io/1.3.2/io.ktor.client.features.websocket/-default-client-web-socket-session/index.html"&gt;&lt;code&gt;DefaultClientWebSocketSession&lt;/code&gt;&lt;/a&gt;. It has two important members:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/index.html"&gt;&lt;code&gt;ReceiveChannel&lt;/code&gt;&lt;/a&gt; named &lt;code&gt;ingoing&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/"&gt;&lt;code&gt;SendChannel&lt;/code&gt;&lt;/a&gt; named &lt;code&gt;outgoing&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;SendChannel&lt;/code&gt; and &lt;code&gt;ReceiveChannel&lt;/code&gt; are the inlet and outlet to a Kotlin &lt;a href="https://kotlinlang.org/docs/reference/coroutines/channels.html#channels"&gt;&lt;code&gt;Channel&lt;/code&gt;&lt;/a&gt;, which is basically like a suspendible queue.&lt;/p&gt;

&lt;p&gt;It's pretty trivial to send and receive some simple messages inside the WebSocket session closure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;ktor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encodedPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Send a message outbound&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sendmessage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello from Android!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;outgoing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;// Receive an inbound message&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;frame&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readText&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, we're missing a bunch of functionality that we had in our OkHttp solution. Namely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We want to send and receive data at the same time, in a loop, and&lt;/li&gt;
&lt;li&gt;We want to get notifications when the connection opens and closes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Parallel send and receive in Ktor
&lt;/h2&gt;

&lt;p&gt;Our goal here is ultimately to dispatch a stream of messages to the &lt;code&gt;outgoing&lt;/code&gt; channel, and to consume a stream of messages from the &lt;code&gt;ingoing&lt;/code&gt; channel, &lt;em&gt;at the same time&lt;/em&gt;. The basic approach we'll use is to launch two bits of work asynchronously, and then wait for them to finalize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;ktor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encodedPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;awaitAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Code that will send messages&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Code that will receive messages&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need some stream of events to trigger the send messages. Adding a button element to our UI makes sense. But, we'd like to model the button clicks as a stream of commands, by the time we dispatch events over the WebSocket.&lt;/p&gt;

&lt;p&gt;Let's first create an extension function to map button clicks into a &lt;code&gt;Flow&amp;lt;Unit&amp;gt;&lt;/code&gt; (credit to &lt;a href="https://stackoverflow.com/a/57914394/695787"&gt;StackOverflow&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;callbackFlow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setOnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;awaitClose&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;setOnClickListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can listen to a flow of events from a button, map them into the format we need, and send them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Hello from Android!"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sendmessage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;outgoing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will work well for the outgoing events. Now, we just need to respond to &lt;em&gt;inbound&lt;/em&gt; events, in the second &lt;code&gt;async&lt;/code&gt; block.&lt;/p&gt;

&lt;p&gt;In this case, there isn't a lot of value to mapping the result. The value we receive over the socket is the contents associated with the &lt;code&gt;"data"&lt;/code&gt; key in the messages. So for example, we might get &lt;code&gt;"Hello from Android!"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;consumeEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readText&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it's all said and done, we end up with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;ktor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encodedPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;awaitAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clicks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sendmessage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello from Android!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;outgoing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;consumeEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readText&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ktor's lifecycle events
&lt;/h2&gt;

&lt;p&gt;OkHttp allowed us to override callbacks on the &lt;code&gt;WebSocketListener&lt;/code&gt;, to get notified of various lifecycle events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WebSocketListener&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// when WebSocket is first opened&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onClosed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// when WebSocket is closed&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ktor doesn't work like that. They suggest some &lt;a href="https://ktor.io/docs/websocket.html#standard-events"&gt;different approaches to recover those events&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The easiest one to recover is the event when the socket opens. It's just the first thing that happens inside of the &lt;code&gt;wss&lt;/code&gt; session closure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;ktor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encodedPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connected to $u!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get more insight into the WebSocket termination, we can expand our processing in our receive block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;consumeEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* as above */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Close&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;reason&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;closeReason&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;await&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
            &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Closing: $reason"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Catching failures is also fairly easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"wss://azgu36n0vf.execute-api.us-east-1.amazonaws.com/Prod"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OkHttp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebSockets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Throwable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"WebSocket failed: ${e.message}"&lt;/span&gt;
            &lt;span class="n"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Well, there you have it: a rough and dirty explanation of how to use OkHttp and Ktor to consume an Amazon API Gateway WebSockets API. 🥳.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jamesonwilliams/android-websocket-client#simple-websocket-app"&gt;The code for this project is available on my GitHub page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>websockets</category>
      <category>android</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>A Brief History of AWS Mobile SDKs and How to Use Them in 2021</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Sat, 26 Dec 2020 23:11:27 +0000</pubDate>
      <link>https://dev.to/jameson/a-brief-history-of-aws-mobile-sdks-and-how-to-use-them-in-2021-420p</link>
      <guid>https://dev.to/jameson/a-brief-history-of-aws-mobile-sdks-and-how-to-use-them-in-2021-420p</guid>
      <description>&lt;p&gt;In this article, I'm going to contextualize some history of AWS' mobile products, and explain when and why you should (or should not) use them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Early Development of Android support at AWS
&lt;/h2&gt;

&lt;p&gt;This year, the &lt;a href="https://github.com/aws-amplify/aws-sdk-android"&gt;AWS SDK for Android&lt;/a&gt; turned 10 🥳. The Android SDK originated as a fork of the &lt;a href="https://github.com/aws/aws-sdk-java"&gt;original Java SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To frame the discussion, let's recall what AWS looked like in 2010. S3 and EC2 were &lt;a href="https://en.wikipedia.org/wiki/Timeline_of_Amazon_Web_Services#Full_timeline"&gt;among our only offerings&lt;/a&gt;. Each was still in its infancy of public availability.&lt;/p&gt;

&lt;p&gt;2010 was the start of the "Web 2.0" revolution, wherein it was no longer enough for a company to be "online." In the Web 2.0 world, you needed to have an API, too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6izqkdtlhwqwxw01amis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6izqkdtlhwqwxw01amis.png" alt="Screenshot-2016-11-01-16.01.29" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In response to the dramatic uptick in demand for APIs, REST began to overtake SOAP as the dominant architectural pattern for client-server exchange on the public Internet. Then-recently created OAuth emerged as the dominant standard for authenticating service requests.&lt;/p&gt;

&lt;p&gt;A key point here is that the Android SDK was envisioned &lt;em&gt;before&lt;/em&gt; these practices had emerged, not in response to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of AWS SDKs
&lt;/h2&gt;

&lt;p&gt;Shifting gears for a moment, let's talk about the AWS SDKs in general. AWS offers SDKs for JavaScript, Python, PHP, .NET, Ruby, Java, Go, Node.js, C++. I'm aware of active projects to support &lt;em&gt;at least&lt;/em&gt; three more popular modern languages.&lt;/p&gt;

&lt;p&gt;We also provide &lt;em&gt;platform&lt;/em&gt;-focused SDKs, like the SDKs for Android, iOS, and front-end JavaScript. &lt;/p&gt;

&lt;p&gt;One immediate question you might have as a front-end dev is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why wouldn't you just use the Java SDK for Android, and the JavaScript SDK for the web? And the (er) ... what about iOS?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why You &lt;em&gt;should&lt;/em&gt; use our Mobile SDKs
&lt;/h2&gt;

&lt;p&gt;There are a number of reasons why you &lt;em&gt;might&lt;/em&gt; want to use one of our mobile SDKs (as opposed to a traditional SDK.)&lt;/p&gt;

&lt;p&gt;First off, if you're an iOS developer, you don't really have an option right now. The iOS SDK is currently the only SDK that supports Objective C or Swift.&lt;/p&gt;

&lt;p&gt;But even on Android, the V2 Java SDK uses some advanced language features, &lt;a href="https://gist.github.com/jamesonwilliams/e5a78e09cbe6e81a5c0b1ccc7f1ff599#using-the-v2-aws-java-sdk-from-android"&gt;that are not supported on Android&lt;/a&gt;. For backend developers, it makes sense to take full advantage of modern JVM features in a modern Java SDK. Unfortunately, the tradeoff is that the Java SDK isn't compatible with Android's runtime, "ART."&lt;/p&gt;

&lt;p&gt;Another reason to prefer the mobile SDKs is that they tend to be vastly stripped down in comparison to the traditional SDKs. For reference, the Android SDK has tens of modules, many of which are focused on data-plane operations. The V2 Java SDK has hundreds of fully-featured modules, useful in managing server infrastructure. It would be wasteful to include all of that extra bloat on a resource constrained device.&lt;/p&gt;

&lt;p&gt;Lastly, AWS Amplify also provides some high-level client libraries which are specifically tailored to use cases commonly encountered on the front-end. For example, most apps need to handle user authentication, save data to the cloud, and record some usage metrics.&lt;/p&gt;

&lt;p&gt;The high-level Amplify libraries are plug-and-play for developers without a lot of experience fiddling with the AWS backend. If you're just starting out with an idea, and want to build it out quick: use Amplify. You'll be able to think about the problem in an idiomatic way, using workflows familiar in your front-end development environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you &lt;em&gt;should not&lt;/em&gt; use the Mobile SDKs
&lt;/h2&gt;

&lt;p&gt;"Great!" you say. So why &lt;em&gt;wouldn't&lt;/em&gt; I use the mobile SDKs?&lt;/p&gt;

&lt;p&gt;The answer to this draws back to my opening point about AWS' history. The SDKs were designed at a time when it was still future-looking to simply communicate with a remote endpoint over REST. That ship has sailed. That's how 95% of new APIs work, now.&lt;/p&gt;

&lt;p&gt;Let's distinguish between two types of client-server interactions. There is a "north-south" interaction, where a public client talks to your service over the Internet, and there is an "east-west" interaction, where your services talk to each other behind a gateway.&lt;/p&gt;

&lt;p&gt;I posit that the AWS SDKs are primarily used for server-to-server communications, to toggle control/admin plane functionalities. In its infancy, AWS prescribed no mechanisms for the data plane. That was left to whatever application you installed in EC2.&lt;/p&gt;

&lt;p&gt;Why does it matter? Mobile devices are always a "north-south" interaction. Once you release your application to the App or Play Store, you lose control over them.&lt;/p&gt;

&lt;p&gt;In a perfect world, we could just implement business logic, and make it available for use over the public Internet. But, we don't live in a perfect world, alas. APIs that serve the public Internet need to be conscious of DDoS attacks, they need to bill/meter resource consumption, throttle over-zealous actors, authenticate and authorize requests from different parties. The list of additional functionalities starts to go on and on.&lt;/p&gt;

&lt;p&gt;In fact, there are so many additional features needed, &lt;em&gt;even before we reach your business logic&lt;/em&gt;, that it really makes sense to put all of this stuff in its own "front-end service." At Amazon, we have only a handful of services that implement this full range of "front-end" functionalities. I'll call these "mobile-blessed services." The leader of the pack is Amazon API Gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  How You Really Ought to Use SDKs with AWS
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Your mobile applications should interact with your business logic through a RESTful endpoint managed by Amazon API Gateway. You should prefer not to make direct calls to most Amazon services directly from a mobile device.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This mantra has some big implications for the utility of the mobile SDKs. As one arbitrary example, let's consider a use case where we want to call Amazon Translate, to translate some text. Instead of using the Amazon Translate support in the &lt;a href="https://github.com/aws-amplify/aws-sdk-ios#aws-sdk-for-ios"&gt;iOS&lt;/a&gt; or &lt;a href="https://github.com/aws-amplify/aws-sdk-android#aws-sdk-for-android"&gt;Android&lt;/a&gt; SDKs, I'm suggesting that you should &lt;em&gt;pick any supported language&lt;/em&gt;, and implement those Translate service calls behind the API Gateway. Render an API response that contains only the bits you need on the mobile device.&lt;/p&gt;

&lt;p&gt;By the time you've implemented this solution, you don't need much on the client. You need a tool to auth users, and a way to sign requests to Amazon API Gateway. Retrofit and Volley are de facto standard REST client libraries.&lt;/p&gt;

&lt;p&gt;Now, there are a lot of different things you can put behind Amazon API Gateway. But for most small-to-medium sized applications, where you don't want to futz about with EC2 instances or containers, you should probably start with an AWS Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exceptions to the Rule &amp;amp; Special Snowflakes
&lt;/h2&gt;

&lt;p&gt;AWS has a few other mobile-oriented services and/or services with a rich set of "front-end" features: Cognito, S3, CloudFront, AppSync. Similarly, any discussion of various AWS services would be replete without guidance around EC2 and DynamoDB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cognito&lt;/strong&gt; is almost a &lt;em&gt;prerequisite&lt;/em&gt; to any serious AWS mobile integration. It provides mechanisms to hook into Google, Apple, and Facebook accounts. You can assign rules around which users get to access what AWS resources. There is a lot of client side logic needed to implement a secure session with Cognito. I recommend using our high-level Amplify Auth for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3&lt;/strong&gt; is actually Amazon's oldest service. It hooks into CloudFront. It has been designed explicitly to serve low-latency content over the public internet. S3, combined with a CDN of some kind, is &lt;em&gt;absolutely&lt;/em&gt; a "front-end" service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AppSync&lt;/strong&gt; is a newer offering, providing a serverless, managed GraphQL endpoint. My team is actively investing a &lt;strong&gt;huge&lt;/strong&gt; amount of energy into the product space. Overall, this is still emerging technology at Amazon. AppSync lacks some basic P0 front-end functionalities like throttling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EC2&lt;/strong&gt; is one of our most popular services. However, there are limited use cases to provision servers directly from a mobile handset over the public Internet. This a &lt;em&gt;prime&lt;/em&gt; example of something you should &lt;em&gt;not&lt;/em&gt; be doing with the AWS Mobile SDKs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamo&lt;/strong&gt; is in a similar boat. It's a well-known bad practice to call a database directly from an application. However, Dynamo is arguably one of the most &lt;em&gt;useful&lt;/em&gt; services AWS provides. We've built an entire front-end product based on transacting data models into and out of Dynamo. Please checkout the &lt;a href="https://docs.amplify.aws/lib/datastore/getting-started/q/platform/android"&gt;Amplify DataStore&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Above, I've tried to provide a bit of context on why we have the products we do, and how you might like to use them. I've laid out some strong opinions. But, as with all solutions in Engineering, you need to look at your use case, and weigh the tradeoffs. I hope this article has moved you closer to a good solution in your unique situation. Happy hacking!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>android</category>
      <category>ios</category>
      <category>amplify</category>
    </item>
    <item>
      <title>Nightly GitHub Contribution Stats with PyGitHub and AWS Lambda</title>
      <dc:creator>Jameson</dc:creator>
      <pubDate>Sat, 26 Dec 2020 01:27:57 +0000</pubDate>
      <link>https://dev.to/jameson/nightly-github-contribution-stats-with-pygithub-and-aws-lambda-8k</link>
      <guid>https://dev.to/jameson/nightly-github-contribution-stats-with-pygithub-and-aws-lambda-8k</guid>
      <description>&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1342446011357290497-855" src="https://platform.twitter.com/embed/Tweet.html?id=1342446011357290497"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1342446011357290497-855');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1342446011357290497&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Today I'm going to show you how to build a GitHub stats scraper using a scheduled-execution AWS Lambda function.&lt;/p&gt;

&lt;p&gt;The Python code inside the Lambda will leverage &lt;a href="https://github.com/PyGithub/PyGithub#pygithub"&gt;PyGitHub&lt;/a&gt; to communicate with the &lt;a href="https://docs.github.com/en/free-pro-team@latest/rest/reference"&gt;V3 GitHub APIs&lt;/a&gt;. The code builds a simple Markdown-flavor report, then publishes it to a &lt;a href="https://stackoverflow.com/a/6767547/695787"&gt;Gist&lt;/a&gt;. The Gist will display the Markdown in rendered form, so you can share a link to it.&lt;/p&gt;

&lt;p&gt;The tables we'll build will look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7imawr0x6mxecmw3o852.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7imawr0x6mxecmw3o852.png" alt="Top ten PR authors in AWS Amplify's GitHub org" width="800" height="922"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmwwee7qffu70n3w4fr08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmwwee7qffu70n3w4fr08.png" alt="Top ten PR reviewers in AWS Amplify's GitHub org" width="800" height="911"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Motivation
&lt;/h3&gt;

&lt;p&gt;AWS Amplify is almost entirely open-source. &lt;a href="https://github.com/aws-amplify"&gt;We use GitHub to host our code&lt;/a&gt;. Our engineers tend to work across our various repositories, and it can be tough to keep track of contributions. Likewise, we want to recognize external parties who have donated their time and mindshare.&lt;/p&gt;

&lt;p&gt;Some questions I'd like this tool to help answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Who are the top &lt;strong&gt;authors&lt;/strong&gt; of pull requests, across our GitHub org?&lt;/li&gt;
&lt;li&gt;Who are the top &lt;strong&gt;reviewers&lt;/strong&gt; of pull requests, across our GitHub org?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are, of course, &lt;em&gt;lots of ways&lt;/em&gt; to contribute to a project. Tracking activity on GitHub pull requests is &lt;em&gt;just one&lt;/em&gt; view of the world. This tool just makes data available. It doesn't prescribe a way for you to analyze it.&lt;/p&gt;

&lt;h3&gt;
  
  
  System Overview
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F02tw02xlp0e8oscpl663.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F02tw02xlp0e8oscpl663.png" alt="Amazon CloudWatch invokes AWS Lambda, which queries GitHub and publishes a Gist." width="639" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The system works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Amazon CloudWatch fires a periodic event&lt;/li&gt;
&lt;li&gt;It causes an AWS Lambda function to execute&lt;/li&gt;
&lt;li&gt;The lambda function queries GitHub's API for contribution data&lt;/li&gt;
&lt;li&gt;The lambda builds a Markdown report&lt;/li&gt;
&lt;li&gt;The lambda publishes the Markdown to a Github gist&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Setting up the Basics
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html"&gt;Creating a lambda function&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/RunLambdaSchedule.html"&gt;setting it up for periodic execution&lt;/a&gt; are fairly straightforward tasks in the AWS Console. After you follow the linked guides from the AWS documentation, you should be left with a dummy Python function that just prints out whatever arguments are passed to it.&lt;/p&gt;

&lt;p&gt;In order to start communicating with GitHub, we'll have to obtain an access token. The PyGitHub documentation shows &lt;a href="https://pygithub.readthedocs.io/en/latest/introduction.html#very-short-tutorial"&gt;a concise example of initializing a client with a GitHub token&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Github&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To obtain one, head over to &lt;a href="https://github.com/settings/tokens"&gt;github.com/settings/tokens&lt;/a&gt;, and click "Generate new token" in the top-right. Give it some name you'll remember. Leave all of the scopes &lt;em&gt;unchecked&lt;/em&gt;, but do check the &lt;strong&gt;gist&lt;/strong&gt; box:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb4x5ie2y99ghhoh53dij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb4x5ie2y99ghhoh53dij.png" alt="Select only the gist scope when creating a GitHub token" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, click "Generate token" at the bottom of the page, and copy-paste the output. Keep the token someplace safe where only you'll have access to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up Dependencies in our Lambda
&lt;/h3&gt;

&lt;p&gt;For simple computations, you can often update a Lambda function directly from the &lt;a href="https://console.aws.amazon.com/lambda/home"&gt;AWS Lambda Console&lt;/a&gt;. However, PyGitHub doesn't exist in the Lambda execution environment. So we'll need to make it available to our Lambda, somehow. The strategy we'll use is to bundle all of our dependencies with the Lambda, so that they're locally available when the code runs.&lt;/p&gt;

&lt;p&gt;AWS Lambda provides documentation to &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-venv"&gt;"Deploy Python Lambda functions with .zip file archives."&lt;/a&gt; But, I'll take a more targeted approach, here. The tasks needed to add the PyGitHub dependency to our Lambda are described below.&lt;/p&gt;

&lt;p&gt;First, use the AWS CLI to find the lambda function you created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;aws lambda list-functions | \
    jq -r '.Functions[].FunctionName'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This outputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;ExampleLambda
demo_function
top_contribs_periodic
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this list, I recognize &lt;code&gt;top_contribs_periodic&lt;/code&gt; as the Lambda that I created earlier.&lt;/p&gt;

&lt;p&gt;Now we're going to ask AWS Lambda to give us a URL from which we can download the function code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda get-function &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--function-name&lt;/span&gt; top_contribs_periodic | &lt;span class="se"&gt;\&lt;/span&gt;
    jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.Code.Location'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The URL will be &lt;em&gt;very long&lt;/em&gt;. It's a pre-signed S3 URL for a zip file in an AWS-managed S3 bucket. It should start off like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://prod-04-2014-tasks.s3.us-east-1.amazonaws.com/snapsh"&gt;https://prod-04-2014-tasks.s3.us-east-1.amazonaws.com/snapsh&lt;/a&gt;....&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we can copy-paste that URL, and use &lt;code&gt;curl&lt;/code&gt; to download the zip file from S3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'&amp;lt;paste-url-from-above&amp;gt;'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; original_lambda.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While we're working on this, we're going to be manipulating zip files that contain a bunch of content &lt;em&gt;at the root level&lt;/em&gt; of the archive. Whenever I'm working with a zip that doesn't have a single top-level directory in it, I like to know what I'm dealing with, first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;zip --show-files original_lambda.zip
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This outputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Archive contains:
  lambda_function.py
Total 1 entries (2217 bytes)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's make a working directory, and unzip the code there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;workingdir
&lt;span class="nb"&gt;cd &lt;/span&gt;workingdir
unzip ../original_lambda.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we're going to use the &lt;code&gt;virtualenv&lt;/code&gt; utility to install our dependencies into this directory. (This step follows the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-venv"&gt;Lambda documentation, "with a virtual environment"&lt;/a&gt; pretty closely.)&lt;/p&gt;

&lt;p&gt;Let's make sure you have &lt;code&gt;virtualenv&lt;/code&gt; installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip3 &lt;span class="nb"&gt;install &lt;/span&gt;virtualenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, create a virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virtualenv myvenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Activate the environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source &lt;/span&gt;myvenv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install PyGitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pygithub 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Almost done. Deactivate the environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deactivate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start creating a new zip file. We'll upload this zip back to AWS Lambda, in a second.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;myvenv/lib/python3.8/site-packages
zip &lt;span class="nt"&gt;-r&lt;/span&gt; ../../../../updated_lambda.zip &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last thing we need to do before re-uploading the code is to package our &lt;code&gt;lambda_handler.py&lt;/code&gt; back into the &lt;code&gt;.zip&lt;/code&gt;. So that we can validate our dependency installation, let's include a very simple use of the PyGitHub. Update the &lt;code&gt;lambda_handler.py&lt;/code&gt; so that it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Github&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Github&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above will look for your GitHub token in the environment, try to instantiate an API client, and the call some simple API, and not crash.&lt;/p&gt;

&lt;p&gt;Save this file into the zip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; -
zip &lt;span class="nt"&gt;-g&lt;/span&gt; updated_lambda.zip lambda_handler.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, upload it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda update-function-code &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--function-name&lt;/span&gt; top_contribs_periodic &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--zip-file&lt;/span&gt; fileb://updated_lambda.zip 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back in the AWS Console, add a new environment variable, &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;, into your Lambda function. Set its value to the one you captured from &lt;a href="https://github.com/settings/tokens"&gt;github.com/settings/tokens&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By now, you should be able to click the "Test" button in the top-right of your AWS Lambda console, to try and invoke the function. The simple Lambda should succeed. But of course, it doesn't really do much yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building out the Core Logic
&lt;/h3&gt;

&lt;p&gt;Well, we succeeded in making a simple call to GitHub from AWS Lambda! Great. Now, let's implement some logic.&lt;/p&gt;

&lt;p&gt;Lets update the Lambda to do something a little more complex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Github&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_recent_contributions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_organization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;aws-amplify&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;repos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_repos&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="n"&gt;month_ago&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pull&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_pulls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;desc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pull&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;month_ago&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pull&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pull&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Recent pull from {0}: {1}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Github&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;list_recent_contributions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above finds all public repos in an organization (&lt;code&gt;'aws-amplify'&lt;/code&gt;), and then queries them for pull requests. The search is in descending order by creation date, meaning that recently created PRs show up first. We stop iterating over the pages of results when we start seeing pulls that are outside the window in which we're interested. (In this case, we're only looking at the last 30 days.)&lt;/p&gt;

&lt;p&gt;You can kind of see the possibilities at this point. You can bounce between the &lt;a href="https://pygithub.readthedocs.io/en/latest/reference.html"&gt;PyGitHub Library Reference&lt;/a&gt; and the &lt;a href="https://docs.github.com/en/free-pro-team@latest/rest/reference"&gt;GitHub REST API reference&lt;/a&gt; and start tuning the logic to meet your needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building Markdown
&lt;/h3&gt;

&lt;p&gt;There are two main challenges in producing the Markdown report.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decoupling document text from Python language syntax&lt;/li&gt;
&lt;li&gt;Producing data tables, row by row&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To achieve the first goal, I'm using a &lt;a href="https://stackoverflow.com/a/10660443/695787"&gt;mutli-line string&lt;/a&gt;. To strip the leading indentation in the Python document, I call &lt;code&gt;textwrap.dedent&lt;/code&gt; on it. The document text has a couple of placeholders that I populate with &lt;code&gt;str.format(...)&lt;/code&gt;, at the end. My &lt;a href="https://gist.github.com/jamesonwilliams/bd188d1682e882046bb3bceb327ec666#file-top_contribs-py-L136-L165"&gt;documentation creation function&lt;/a&gt; looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;textwrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dedent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    # Top Contributors, Last 30 Days

    This is a list of the top contributors to the aws-amplify Github org&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s public repos.
    Contributions from the last 30 days are considered.
    This document is updated by a cron job every day.
    Contributors are from AWS and from the community.
    Contribution counts are a running sum of a user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s contributions across all repos.

    ### Top 10 Authors
    {0}
    ### Top 10 Reviewers (by total comments)
    {1}

    -----------------------

    Last updated {2}.

    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authors_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reviewers_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what about the &lt;code&gt;authors_table&lt;/code&gt; and &lt;code&gt;reviewers_table&lt;/code&gt;? How are those obtained? To build the Markdown tables, I use some more string templates. I have &lt;a href="https://gist.github.com/jamesonwilliams/bd188d1682e882046bb3bceb327ec666#file-top_contribs-py-L112-L134"&gt;a utility function which can transform a &lt;code&gt;dict&lt;/code&gt; into a Markdown table&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;top_ten_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;row_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;|{0}|{1}|{2}|&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Rank&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val_label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;row_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--------&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--------&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--------&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;item_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_list&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;row_template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Publishing a Gist
&lt;/h2&gt;

&lt;p&gt;The last big piece of this project is to publish the outputs somewhere.&lt;/p&gt;

&lt;p&gt;There are a number of cool possibilities here. If you're using some static hosting solution like Amplify Hosting or GitHub Pages, your published Markdown could get stylized with whatever rules your existing web app applies. You could pretty much publish the Markdown anywhere.&lt;/p&gt;

&lt;p&gt;I had considered publishing the output to my &lt;a href="https://github.com/jamesonwilliams/jamesonwilliams.github.io"&gt;GitHub Pages repository&lt;/a&gt;, and then letting &lt;a href="https://nosemaj.org"&gt;my Jekyll site&lt;/a&gt; render it with its stylesheet.&lt;/p&gt;

&lt;p&gt;But, in the interest of keeping things simple, let's just put it into a Gist for right now.&lt;/p&gt;

&lt;p&gt;The Gist APIs are actually a weak-spot in the PyGitHub implementation, IMO. What I'd like to do is just have a &lt;code&gt;user.update_gist(...)&lt;/code&gt; function available. Unfortunately, there isn't anything exactly like that. Instead, we have to related capabilities available:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search all of my Gists, get a handle to one, and call &lt;code&gt;.update(...)&lt;/code&gt; on it;&lt;/li&gt;
&lt;li&gt;Create another new Gist.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Well, fine. Let's try to encapsulate those two things into &lt;a href="https://gist.github.com/jamesonwilliams/bd188d1682e882046bb3bceb327ec666#file-top_contribs-py-L167-L189"&gt;a single utility method&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_gist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InputFileContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Looking for matching Gists....&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;gist&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_gists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;gist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found a matching Gist. We&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll updated it.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;gist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No existing Gist, creating a new one....&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_gist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;public&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will look for an existing Gist with a given description. If it finds one, then it updates it. If it doesn't find one, it continued to create a new Gist with that description. 🥳&lt;/p&gt;

&lt;p&gt;When you run this function, it will save the provided content to Gist. Since it currently hard-codes &lt;code&gt;'contrib.md'&lt;/code&gt; as the filename, the resulting URL you end up with will look like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://gist.github.com/yourusername/uniqueid#file-contrib-md"&gt;https://gist.github.com/yourusername/uniqueid#file-contrib-md&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Wrapping Up
&lt;/h3&gt;

&lt;p&gt;That's most of the nuts and bolts to it. &lt;a href="https://gist.github.com/jamesonwilliams/bd188d1682e882046bb3bceb327ec666#file-top_contribs-py"&gt;The complete script I'm using is available here, on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The output of that script is &lt;a href="https://gist.github.com/jamesonwilliams/fe7c3be0ff2c4a9233ef9cb19d4aa051#top-contributors-last-30-days"&gt;visible here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let me know what you think! What features should I add to it next? Happy hacking, and happy holidays. 🎄&lt;/p&gt;

</description>
      <category>github</category>
      <category>awsamplify</category>
      <category>serverless</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
