<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>workshop · @jnjosh’s internet weblog</title>
    <link>https://jnjosh.com/workshop/</link>
    <description>Building things — software, electronics, watchmaking.</description>
    <language>en</language>
    <lastBuildDate>Sun, 09 Feb 2025 16:00:10 -0500</lastBuildDate>
    <atom:link href="https://jnjosh.com/workshop/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Becoming an Amatuer Watchmaker: Servicing a Vintage Watch</title>
      <link>https://jnjosh.com/workshop/posts/elgin-watch/</link>
      <pubDate>Sun, 09 Feb 2025 16:00:10 -0500</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/elgin-watch/</guid>
      <description>&lt;p&gt;Last year I dramatically fell into the world of watchmaking. I&amp;rsquo;ve often been mildly interested in watches and how they work but just assumed it was something too hard to do. I don&amp;rsquo;t remember where it started exactly this time, somewhere between Nat and I just deciding to try a &lt;a href=&#34;https://www.namokimods.com&#34;&gt;DIY watchmaking kit&lt;/a&gt; or getting lost in the &lt;a href=&#34;https://www.youtube.com/@WristwatchRevival&#34;&gt;Wristwatch Revival&lt;/a&gt; YouTube videos, but when I say dramatically fell into the world, it&amp;rsquo;s not an exaggeration. I&amp;rsquo;ve fully set up a watchmaking desk, acquired a lot of strange and specific tools, and have now completed my second service. Between November and February I disassembled, cleaned, lubricated, and assembled a vintage Elgin watch I found on eBay—I learned a lot, did dumb things, acquired new tools, and finished the process with a new watch in my watch box.&lt;/p&gt;&#xA;&lt;!-- more --&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20250209/elgin/watchdesk.jpg&#34; alt=&#34;Desk with watchmaking tools&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;The first watch I serviced was a learning experience. I actually did it 3 times because each time I had done something wrong and decided to just start from scratch. It also was a simple watch—no date, not automatic, etc—but also a very difficult watch in that it was hard to get it to perform well as it was a old and cheap 1-jeweled watch movement. I got through it and the watch is now in Nat&amp;rsquo;s watch collection, but I definitely plowed through it making a lot of mistakes.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20250209/elgin/firstwatch.jpg&#34; alt=&#34;Sheffield Acrylic Watch, my first service&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;My next watch would be the real test of what I learned. So I started looking for things to repair on eBay. I knew that a watch really needing restoration would probably be too hard, so I looked for things that were older but claimed to at least still work in some form. This may not surprise you but there are a lot of options for old watches on eBay. After browsing I landed on this vintage Elgin with a Linen Dial. I just loved the style of the linen and the way the numbers look—this Z-shaped 2 gets me every time. Even though I had gone through a service already this would be my first with some experience so I wanted to also expand a little—this one had a calendar wheel too, which the previous watch and learning movements (a Seagull ST36) didn&amp;rsquo;t have. So I bought it and excitedly waited for it to arrive.&lt;/p&gt;&#xA;&lt;p&gt;About a week later in early November it showed up. I was pretty lucky. This watch was a bit of a risk, while the eBay seller said it was running—they couldn&amp;rsquo;t figure out how to open the case to inspect the movement or share photos of it. My naïvety shined here and I just thought, I&amp;rsquo;m sure it&amp;rsquo;s fine. I can definitely figure out how to open it. It was nice and the dial was better than I imagined. So I took a few photos and tested it on the timegrapher. It was running but a bit poorly, losing about 1.5 minutes a day.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20250209/elgin/elgin.jpg&#34; alt=&#34;The watch straight from eBay, performing at -89 seconds per day&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;So here we go, let&amp;rsquo;s get this thing open and start cleaning it. Oh wait. But. It&amp;rsquo;s not a normal watch. How do I open this thing? I studied it for a while, longer than I&amp;rsquo;d like to admit, and just couldn&amp;rsquo;t figure it out. The back was pretty scratched up so it looked like others had tried to open this too in the watch&amp;rsquo;s history. Then I remembered one of the many many episodes of Wristwatch Revival I watched where he had to remove the movement from the crystal side. It turns out this watch is a &lt;a href=&#34;https://en.wikipedia.org/wiki/Monocoque&#34;&gt;monocoque&lt;/a&gt; shell watch—meaning it was one piece. I had to remove the crystal first, but how? A new tool!&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20250209/elgin/tools.jpg&#34; alt=&#34;A crystal lift tool, so metal&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;This—the most metal tool ever 🤘—is a crystal lift tool that squeezes the crystal just enough to be removed from the edges of the metal watch case. After you remove the crystal from the front of the watch, the movement can be removed easily. Also this type of case needs to use a s&lt;a href=&#34;https://blog.watchdoctor.biz/2023/10/29/what-is-a-split-stem-2/&#34;&gt;plit stem&lt;/a&gt; since you can&amp;rsquo;t easily remove the crown and stem—which was a scary process of pulling the crown until it detaches. And now I can pull the movement out and start inspecting it.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20250209/elgin/movement.jpg&#34; alt=&#34;on the left, the Elgin 315 movement, on the right the dial side with calendar wheel&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;The movement is an &lt;a href=&#34;https://ranfft.org/caliber/3378-Elgin-315&#34;&gt;Elgin 315&lt;/a&gt;, which looking deeper is based on an &lt;a href=&#34;https://ranfft.org/caliber/view?id=840&#34;&gt;A. Schild 1802/1803&lt;/a&gt; caliber. And it was dirty! Whoever serviced this thing last time really enjoyed using lubrication and often things were just stuck to each other or there were pooled areas of lubrication. It was kind of gross. Also it introduced my second big problem. I took macro photos of each step of the disassembly so if I needed to refer to something I could. Taking off the calendar wheel unfortunately was too easy, it all was under tension and so over-lubricated that once I loosened the screws of the cover plate it all kind of just popped out. Thankfully I didn&amp;rsquo;t lose any of the tiny parts, but I don&amp;rsquo;t know exactly how they all go back on since this was my first project with calendar works. This is a forshadowing moment for later. Anyway, I got everything disassembled and cleaned through my ultrasonic and thouroughly dried.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20250209/elgin/disassembled.jpg&#34; alt=&#34;the movement, completely disassembled&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;Time to reassemble! First part of this I recorded a video so you can follow along. Most of the reassembly went well, I had ordered a new mainspring because I don&amp;rsquo;t yet have a mainspring winder and it&amp;rsquo;s a pretty expensive tool—new mainsprings are cheap if you can find them. Getting the main part of the watch back together has become a bit more routine now that I&amp;rsquo;ve done the process a few times. Also at this point all the parts were still in good shape after I inspected them, so didn&amp;rsquo;t need to order anything new yet.&lt;/p&gt;&#xA;&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;&#xA;&#x9;&#x9;&#x9;&lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube-nocookie.com/embed/ZSnWqfVEs-Y?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;&#xA;&#x9;&#x9;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;Then comes the calendar works. Remember earlier when everything stuck and I wasn&amp;rsquo;t able to see how it all goes back together? Well this is when it bit me. Thankfully it&amp;rsquo;s not too hard to reason about what goes where and some deep studying of the photos I did get helped a lot. Bad news is some parts do need to go in the right way and &lt;em&gt;not&lt;/em&gt; upside down. Now listen, when it comes to watchmaking, one thing I&amp;rsquo;ve learnd pretty well is to not force things. These parts are small and delicate. Forcing is a bad idea. So, of course, after getting the calendar works back together but with an upside down lever holding the calendar wheel in place, it was not turning. This is when I forced it and… well tore the teeth off this hour wheel. Sometimes I guess you have to relearn lessons the hard way.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20250209/elgin/broken-hourwheel.jpg&#34; alt=&#34;an hour wheel, with broken teeth on the top&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;So… I went looking for another hour wheel and found a new old stock one and an old one on eBay, both about $5-10. So I bought the eBay one first but later thought I&amp;rsquo;d rather have the newer one. So I waited for a week and they both arrived on about the same day. The new one however didn&amp;rsquo;t fit. It was the right size but it was not as deep and the teeth didn&amp;rsquo;t line up, so the old one it is and now I have an extra part. I&amp;rsquo;m starting a collection of extra parts now I guess. But now it works and look at that calendar moving nicely but without needing to force it.&lt;/p&gt;&#xA;&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;&#xA;&#x9;&#x9;&#x9;&lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube-nocookie.com/embed/OipP_SdgND0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;&#xA;&#x9;&#x9;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;On the home stretch now! Just need to get it back in the case and wrap it up. It&amp;rsquo;s never that easy of course. I pulled the crown stem out to get it back in it&amp;rsquo;s case and accidentally popped the yoke off it&amp;rsquo;s place—the yoke a small part that helps hold tension around the stem when changing between winding and time setting. That gave me one more practice run on assembling the calendar works so… hey practice! Wrapping this up I got the dial and hands back on, reapplied the crystal and put on a new strap. I chose this blue leather one because I thought it went really well with the dial. I was able to get the performance improved a lot from losing a minute plus a day to hovering around +/- 3 seconds per day. Also I was able to improve the amplitude a lot even getting over 300º in some measurements.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20250209/elgin/completed.jpg&#34; alt=&#34;the completed watch on blue strap, with -1 seconds/day timing&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;Really happy with how this turned out. I&amp;rsquo;m still so new to this hobby and still make all the mistakes you&amp;rsquo;d expect. It&amp;rsquo;s really interesting as some times I feel like I&amp;rsquo;m using similar skills to engineering. I&amp;rsquo;m reasoning about how things fit together, I&amp;rsquo;m building up a stack in my head of what connects to what, I&amp;rsquo;m testing and measuring improvements, and when things go bad I&amp;rsquo;m in full debugger mode. Watchmaking has become that thing that exercises my love for programming but gets me away from a computer all the time. It&amp;rsquo;s been so fun to learn and I&amp;rsquo;m looking forward to what&amp;rsquo;s next. I&amp;rsquo;ve been busy on eBay and have given myself quite the backlog.&lt;/p&gt;&#xA;&lt;p&gt;Do you want to nerd out about watches with me? I have been posting a lot of these photos and videos to &lt;a href=&#34;https://www.instagram.com/jnjosh/&#34;&gt;Instagram&lt;/a&gt; so feel free to reach out there or on &lt;a href=&#34;https://mastodon.social/@jnjosh&#34;&gt;Mastodon&lt;/a&gt;. I&amp;rsquo;m still quite new in this but I&amp;rsquo;m definitely deep in that &amp;ldquo;I can talk about this for hours&amp;rdquo; level.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Be a Goldfish</title>
      <link>https://jnjosh.com/workshop/posts/be-a-goldfish/</link>
      <pubDate>Sun, 02 Feb 2025 19:24:36 -0500</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/be-a-goldfish/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s been a bit over 10 years since I first became an engineering manager—minus a couple non-manager roles in the startup world as a lead engineer. That realization and my normal end of year productivity reboot has had me thinking a lot about how I operate outside of the core mechanics of how I do things—namely how I have changed and how things operate in my head. The thing is, I&amp;rsquo;ve always been really good at dropping work out of my head at the end of the day. Usually only getting stuck on work topics if things are really bad or I&amp;rsquo;m really perplexed or really upset about something. Something not dropping easily at the end of the day is kind of an indicator for me that I need to change or address it. I&amp;rsquo;ve been wanting to write about this for awhile but watching &lt;a href=&#34;https://www.youtube.com/watch?v=LtEs4ELZuDQ&#34;&gt;Cinema Therapy&amp;rsquo;s episode&lt;/a&gt; on one of my favorite television shows—&lt;em&gt;&lt;strong&gt;&lt;a href=&#34;https://www.imdb.com/title/tt10986410&#34;&gt;Ted Lasso&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;—brought the idea back.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;You know what the happiest animal on Earth is? It&amp;rsquo;s a goldfish. It has a 10-second memory. Be a goldfish.&lt;/p&gt;&#xA;&lt;p&gt;— Ted Lasso (Jason Sudeikis)&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;!-- more --&gt;&#xA;&lt;p&gt;January 2025 was a rough month for me—a continuation of an already difficult December where I had my first experience with COVID. Not only were there a lot of really sad and difficult things going on at home but it was a rough time coming back to work. All the excitement for 2025 was suddenly real and … woah we aren&amp;rsquo;t ready. Large projects need planned, teams need roadmaps, everyone is excited but also frustrated and wanting answers. There were days after long stretches of meetings where I just wanted to curl up and cry. But hey… &amp;ldquo;Be a goldfish.&amp;rdquo;. I had to really lean into this. When things are hard it&amp;rsquo;s super easy to worry on things that you don&amp;rsquo;t normally worry about.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve worked with a lot of really opinionated engineers who focused so much on their idea that they were often blinded by other ideas. I can remember ending days furious at them. Like &amp;ldquo;I&amp;rsquo;ve been doing this too for years, can you at least look at my point of view?&amp;rdquo;. &amp;ldquo;Be a goldfish&amp;rdquo;. I didn&amp;rsquo;t let it get too deep beyond that first day. Some of those engineers are now good friends.&lt;/p&gt;&#xA;&lt;p&gt;I recently read the book &lt;a href=&#34;https://a.co/d/a2tIUza&#34;&gt;&amp;ldquo;Rise&amp;rdquo; by Patty Azzarello&lt;/a&gt; and it was right there too, but worded differently. It tells us &amp;ldquo;Don&amp;rsquo;t stay angry&amp;rdquo; and &amp;ldquo;Keep moving forward&amp;rdquo;—isn&amp;rsquo;t that what a goldfish does? These are obviously more related to how you react to encounters and experiences, but I don&amp;rsquo;t see why thinking about it like a goldfish is all that different. Someone is abnormally short with you? Maybe they too had a bad day. Be a goldfish.&lt;/p&gt;&#xA;&lt;p&gt;As engineering managers, we are often faced with scenarios where we think we might have the right answers but almost every time it&amp;rsquo;s better to work with the team to get there. That can be hard to accept. Sometimes you might think your idea was better and resent the different path. Be a goldfish.&lt;/p&gt;&#xA;&lt;p&gt;As leaders, we have to make hard decisions and say no to good ideas—often risking uncomfortable conflict or just the knowledge that the team disagrees with a decision. Be a goldfish.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s not perfectly applicable to all scenarios—some things do need to be seriously addressed. However, for me I&amp;rsquo;ve found that most day-to-day challenges, conflicts, disagreements, imposter syndrome feelings, and more have always been good to feel in the moment and evaluate if I need to learn from it or not, but then move forward and be a goldfish.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Barbecue sauce.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;</description>
    </item>
    <item>
      <title>Automating LUTs on Video</title>
      <link>https://jnjosh.com/workshop/posts/automate-luts/</link>
      <pubDate>Mon, 27 May 2024 14:43:58 -0400</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/automate-luts/</guid>
      <description>&lt;p&gt;My wife and I have worked on &lt;a href=&#34;https://www.youtube.com/@natandjosh&#34;&gt;our YouTube channel&lt;/a&gt; from time to time and one thing that comes up regularly is how we capture video differently. We normally use our iPhones as that&amp;rsquo;s the sort of best middle ground of good video and convenience. However, I often get fancy and use apps like the new-ish &lt;a href=&#34;https://www.blackmagicdesign.com/products/blackmagiccamera?gad_source=1&amp;amp;gclid=Cj0KCQjw3tCyBhDBARIsAEY0XNnpfG6F-RXRVSEfO1VFIH3-FirVydbEIQ8s0ll5wcHh9-9P3dyM9E0aAugEEALw_wcB&#34;&gt;Blackmagic Camera App&lt;/a&gt; to record ProRes 422 HQ / Apple Log footage on my iPhone 15 Pro. That&amp;rsquo;s great and all but a lot of the time, Nat will edit the videos as I&amp;rsquo;m at work or something. So then she has to color correct the videos and the pipeline just gets really annoying. Of course, I could just stop being so fancy and just record in the camera app with the same settings as her footage. Hmmmmm… should I?&lt;/p&gt;&#xA;&lt;p&gt;Nah, of course not! I&amp;rsquo;ll just script away the problem. So I wrote a bash script that takes in my Log profile based footage, applies the LUT Apple provides to bring it back to Rec.709, and also samples it down to regular ProRes 422—to save some space.&lt;/p&gt;&#xA;&lt;p&gt;First, you&amp;rsquo;ll need a couple tools. Like I said you need a LUT to apply. Thankfully Apple provides a nice LUT on their &lt;a href=&#34;https://developer.apple.com/download/all/?q=Apple%20log%20profile&#34;&gt;developer site&lt;/a&gt;—I use the &lt;code&gt;AppleLogToRec709-v1.0.cube&lt;/code&gt; option as I&amp;rsquo;m recording in Apple Log and Rec.709 is good enough for our YouTube purposes. Other LUTs are out there in the community too if you&amp;rsquo;d prefer something else.&lt;/p&gt;&#xA;&lt;p&gt;Next you&amp;rsquo;ll need a tool that can apply this lut to your video. If you haven&amp;rsquo;t used it, &lt;a href=&#34;https://ffmpeg.org&#34;&gt;ffmpeg&lt;/a&gt; is a tool for working with audio and video and… it is a lot, but it&amp;rsquo;ll do great for what we need.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ brew install ffmpeg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Now you just need to run &lt;code&gt;ffmpeg&lt;/code&gt; with some settings, provide it the LUT and go. Here&amp;rsquo;s what the settings I&amp;rsquo;m using do:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;ffmpeg &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-hide_banner &lt;span class=&#34;se&#34;&gt;\ &lt;/span&gt;  &#x9;&#x9;&#x9;&lt;span class=&#34;c1&#34;&gt;## Hide command line updates, quiet mode&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-loglevel error &lt;span class=&#34;se&#34;&gt;\&#x9;&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;c1&#34;&gt;## Only show errors, I don&amp;#39;t want it to be verbose&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-i &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#x9;&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;c1&#34;&gt;## The INPUT file name&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;nv&#34;&gt;lut3d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;lut.cube&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#x9;&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;c1&#34;&gt;## Apply the LUT itself&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-c:v prores_ks &lt;span class=&#34;se&#34;&gt;\&#x9;&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;c1&#34;&gt;## Encode into ProRes, still want some quality ;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-profile:v &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#x9;&lt;/span&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;c1&#34;&gt;## Lower to ProRes 422, not&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-c:a copy &lt;span class=&#34;se&#34;&gt;\&#x9;&lt;/span&gt;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;c1&#34;&gt;## Copy settings in output&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;Converted-&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;c1&#34;&gt;## The OUTPUT file name&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Like I said, FFMPEG is a lot, but you can generally get it to do what you need with some tinkering. So with that, I wrapped it up into a nice little script:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# The I&amp;#39;m too fancy script&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;DIRECTORY&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;OUTPUT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;pushd&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;DIRECTORY&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt; &amp;gt; /dev/null&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; filename in *&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Applying LUT to &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;ffmpeg &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-hide_banner &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-loglevel error &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-i &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-vf &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;nv&#34;&gt;lut3d&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;AppleLogToRec709-v1.0.cube&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-c:v prores_ks &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-profile:v &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;-c:a copy &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;OUTPUT&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;/Converted-&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&#x9;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;popd&lt;/span&gt; &amp;gt; /dev/null&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Now I can just process the files easily…&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ./convert ./Input /Users/jnjosh/Desktop/Convert/Output&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;…and then share the resulting Rec.709 files for editing. I&amp;rsquo;m sure there is a nice way to do this out there but that wouldn&amp;rsquo;t be so fun.&lt;/p&gt;</description>
    </item>
    <item>
      <title>So I&#39;m starting another blog about Italian stuff</title>
      <link>https://italy.jnjosh.com</link>
      <pubDate>Sat, 13 Jan 2024 16:48:08 -0500</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/italy-blog/</guid>
      <description>&lt;p&gt;As little as I blog here it might be a surprise that I&amp;rsquo;m starting a different blog but here we are. Over the last year, I&amp;rsquo;ve spent a lot of time learning Italian, learning how to cook Italian, and learning about my family history and genealogy. So much that I just want to talk about it all the time. Most people don&amp;rsquo;t want to hear me talk about Italy all the time so it&amp;rsquo;s an outlet. If you want to follow along, &lt;a href=&#34;https://italy.jnjosh.com&#34;&gt;check it out&lt;/a&gt;!&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://jnjosh.com/workshop/posts/italy-blog/&#34;&gt;Permalink&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>2023: Entertainment Year in Review</title>
      <link>https://jnjosh.com/workshop/posts/2023-in-review/</link>
      <pubDate>Sun, 31 Dec 2023 20:06:48 -0500</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/2023-in-review/</guid>
      <description>&lt;p&gt;I almost forgot to blog this year! It&amp;rsquo;s now the last minute—9pm on December 31—and the only thing I could think of was to write was a sort of &amp;lsquo;Year in Review&amp;rsquo; of the movies, TV, and music I&amp;rsquo;ve enjoyed this year. It is something I&amp;rsquo;ve wanted to do before anyway, so why not start the tradition? So if you&amp;rsquo;ll indulge me here are a few interesting movies, shows, and songs I was entertained by in 2023.&lt;/p&gt;&#xA;&lt;h2 id=&#34;movies&#34;&gt;Movies&lt;/h2&gt;&#xA;&lt;p&gt;Well this certainly wasn&amp;rsquo;t my most busy year watching Movies. I think I watched a lot of TV instead. Still, I was able to &lt;a href=&#34;https://letterboxd.com/jnjosh/year/2023/&#34;&gt;watch 52 movies&lt;/a&gt;—a few even in Italian.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20231231/movies-2023.png&#34; alt=&#34;Movies&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;I rewatched a lot, including going through all the Star Trek films (&amp;ldquo;Hello, Computer&amp;rdquo;), rewatching all the Indiana Jones movies, and a bunch of classic movies over Christmas break. Some stand out highlights for me this year were:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://letterboxd.com/jnjosh/film/one-hundred-steps/&#34;&gt;&amp;ldquo;One Hundred Steps&amp;rdquo;&lt;/a&gt;&lt;/strong&gt; (Italian: &amp;ldquo;I cento passi&amp;rdquo;)—a based on a real story about a small town in Sicily. As I&amp;rsquo;ve been learning Italian I&amp;rsquo;ve also been trying to learn about modern Italian history. This is one of a few I watched but it had me reading wikipedia about the events it was based on.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://letterboxd.com/jnjosh/film/indiana-jones-and-the-dial-of-destiny/&#34;&gt;&amp;ldquo;Indiana Jones and the Dial of Destiny&amp;rdquo;&lt;/a&gt;&lt;/strong&gt;—Say what you want about bringing back another Indy movie after Crystal Skull, but I had a really fun time with this movie. It reminded me of the fun of an Indy movie but didn&amp;rsquo;t try to hide too much from age. It all culminates in a wild fantastical ending that had me just grinning like a kid. This is a fun movie.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://letterboxd.com/jnjosh/film/millennium/&#34;&gt;&amp;ldquo;Millenium&amp;rdquo;&lt;/a&gt;&lt;/strong&gt;—Not a highlight but really a lowlight. I watched this movie several times when I was a kid and my memory of it was a fascinating time travel story. I rewatched it this year and it&amp;rsquo;s not a good movie. My memory of it was better. I miss that movie. It&amp;rsquo;s risky to rewatch movies from childhood.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;tv&#34;&gt;TV&lt;/h2&gt;&#xA;&lt;p&gt;TV was much more exciting of a year for me. I&amp;rsquo;ve struggled to remember everything I watched that was really good—probably forgetting something. I wish I kept up with TV like I do movies so I don&amp;rsquo;t have a cool image like I get from Letterboxd. Still, some notable highlights:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Last of Us&amp;rdquo;&lt;/strong&gt;—I thought I was bored with zombie-based shows after getting tired of The Walking Dead years ago. This however told such a great story—especially the Nick Offerman episode. I rarely listen to making of podcasts but tuned in weekly for this one.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Foundation&amp;rdquo;&lt;/strong&gt;—This show is hard to describe, it&amp;rsquo;s science fiction spanning generations touching on themes of religion and fascism, but it has me on the edge of my seat. Every season ends with me wanting more.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Doctor Who&amp;rdquo;&lt;/strong&gt;—If you know me you know I&amp;rsquo;m a whovian. It&amp;rsquo;s a show that can do anything, anywhere, and anywhen. The 4 specials brought back a Doctor as the 14th doctor and introduced a new 15th doctor. Doctor Who is my deserted island entertainment.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Star Trek: Picard Season 3&amp;rdquo;&lt;/strong&gt;—This show accomplished such a wonderful end to the series. It was able to bring fan service for the story, not just because. It was an unnecessary good bye to the characters but I&amp;rsquo;m glad I had a chance to see them all again. Only problem was lack of Wesley Crusher!&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Star Trek: Strange New Worlds Season 2&amp;rdquo;&lt;/strong&gt;—Some of the most fun I&amp;rsquo;ve had watching TV this year. It&amp;rsquo;s fun and challenges the ideas of Star Trek. Plus we got to see Spock sing.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&amp;ldquo;Guida astrologica per cuori infranti&amp;rdquo; (&amp;ldquo;An Astrological Guide for Broken Hearts&amp;rdquo;)&lt;/strong&gt;—It&amp;rsquo;s not a new show this year but we binged it all. On the surface it&amp;rsquo;s a simple romantic comedy but the characters are so good and fun to watch them go through hijinx. I&amp;rsquo;ve rewatched it a few times too to practice Italian.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&amp;ldquo;My Brilliant Friend&amp;rdquo;&lt;/strong&gt;—A show about two friends growing up and growing apart. It&amp;rsquo;s frustrating, maddening, fun, annoying, exciting. I wasn&amp;rsquo;t expecting to think about this show as much as I have.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&amp;ldquo;GenV&amp;rdquo;&lt;/strong&gt;—The last show I watched of the year. It&amp;rsquo;s in &amp;ldquo;The Boys&amp;rdquo; universe and it shows—this is not a superhero show for kids. It&amp;rsquo;s so on point with modern life all through the lens of superheros. I&amp;rsquo;ve never seen a spinoff fit so well into it&amp;rsquo;s original show while also being its own thing.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;music&#34;&gt;Music&lt;/h2&gt;&#xA;&lt;p&gt;As you may have guessed I mostly listened to Italian music this year. In fact here&amp;rsquo;s a &lt;a href=&#34;https://open.spotify.com/playlist/3BmhPNe46a0CEmJvf9itpf?si=9ff21c59182647ba&#34;&gt;playlist of all my favorites&lt;/a&gt;. Some songs I sing along with and get stuck in my head have been:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&amp;ldquo;L&amp;rsquo;ultima notte&amp;rdquo; by ARIETE&lt;/li&gt;&#xA;&lt;li&gt;&amp;ldquo;Di Notte&amp;rdquo; by Ernia, featuring Carl Brave &amp;amp; Sfera Ebbasta&lt;/li&gt;&#xA;&lt;li&gt;&amp;ldquo;Ciao Ciao&amp;rdquo; by La rappresentante di lista&lt;/li&gt;&#xA;&lt;li&gt;&amp;ldquo;La musica no c&amp;rsquo;è&amp;rdquo; by Coez&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Well, that&amp;rsquo;s a year. I expect next year will be mostly the same, just more Italian and maybe more films.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Why Monorepos?</title>
      <link>https://jnjosh.com/workshop/posts/why-monorepos/</link>
      <pubDate>Thu, 06 Oct 2022 23:27:17 -0400</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/why-monorepos/</guid>
      <description>&lt;p&gt;I really like the idea of &amp;ldquo;asking a lot of dumb questions to learn fast&amp;rdquo; and while I don&amp;rsquo;t think this is a dumb question it&amp;rsquo;s kind of dumb in that it&amp;rsquo;s a controversial question on the internet. Maybe I can learn something at least? So here it goes.&lt;/p&gt;&#xA;&lt;p&gt;Why are so many developers so smitten with monorepos?&lt;/p&gt;&#xA;&lt;p&gt;What&amp;rsquo;s a monorepo? From &lt;a href=&#34;https://en.wikipedia.org/wiki/Monorepo&#34;&gt;Wikipedia&lt;/a&gt;, it&amp;rsquo;s a &amp;ldquo;software development strategy where code for many projects is stored in the same repository&amp;rdquo;. All your different apps, libraries, and utilities are in one large repository, a &amp;ldquo;shared codebase&amp;rdquo;. As you can imagine stashing a bunch of disparate projects into one large repository leads to some problems, namely size. This leads to tools like &lt;a href=&#34;https://rushjs.io&#34;&gt;Rush&lt;/a&gt; for Javascript developers to help manage the monorepo. A lot of large tech companies, including those I&amp;rsquo;ve worked with use this. Continuing on Wikipedia we see some benefits:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Ease of code reuse&lt;/strong&gt; - when all the code is in one big directory you can just reference anyone else&amp;rsquo;s code (libraries ore even projects) without needing any sort of dependency management or semantic versioning.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Simplified dependency management&lt;/strong&gt; - Since many projects in the monorepo may depend on similar third-party libraries, they can be optimized and provided consistently by the monorepo.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Atomic commits&lt;/strong&gt; - When you need to make a large change affecting multiple projects and libraries, you can push it all together without chaining releases. For example, a model change can be made in a library, the frontend, and the backend all in one pull request.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Large-scale code refactoring&lt;/strong&gt; - Similarly to atomic commits, you can do large refactors across multiple projects in the course of one pull request—making sure everything continues to work as expected after the pull request is merged.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Collaboration across teams&lt;/strong&gt; - With all projects, owned by different teams, the code ownership model is much more flexible. You can contribute to and receive contributions from other teams.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve spent most of my career working on native software (iOS, macOS, Windows, etc), but did do some web development here and there. I say that because I think I&amp;rsquo;ve mostly—but not exclusively—seen monorepos coming from web development teams. Maybe that&amp;rsquo;s why this is such a strange idea to me? I know it&amp;rsquo;s not quite this bad but it reminds me of development before adopting version control systems where we&amp;rsquo;d just store the code on a network drive—everything in one directory is easier!&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s a bit extreme and when I think about this more critically there are some clear benefits, namely &lt;strong&gt;atomic commits&lt;/strong&gt;. One team I worked on we built a distributed codebase where a rich model layer was in it&amp;rsquo;s own repository. In the early days when a team owned that project, it was great. Contributions were open, but the team aimed to work with the frontend team to make sure their needs were met by the time it was needed. No silos, everyone talked! As things change, that project became ownerless and people just started making changes—forced to do a &lt;a href=&#34;https://semver.org&#34;&gt;semantic versioned&lt;/a&gt; release for every change. This would be fine if there were many consumers of that library, but there was one client app.&lt;/p&gt;&#xA;&lt;p&gt;This was a design problem that multiple repos highlighted. A design problem that would have made more sense for it to start in the repository of the client application, and only moved to a separate repo if more applications started using the project. So for a single application, even heavily factored into core components and libraries, I think I&amp;rsquo;m newly open to that just being one repo. You can still maintain code ownership with Github features, and even publish semantic versions if you want, but can take the benefit of those &lt;strong&gt;atomic commits&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Maybe that is my core struggle with this idea. The above project was all one iOS application written in Swift with the occasional Objective-C. Should there be another iOS app in that repo? Should the Android app have been in that repo too? I don&amp;rsquo;t think so but maybe that&amp;rsquo;s not just about multiple applications, but also multiple technologies all in one large repo. Maybe this is why web folks like it, when everything is Javascript why draw boundaries!?&lt;/p&gt;&#xA;&lt;p&gt;Also, what about the rest of those benefits? Anyone can change anything? So you save the effort of managing dependencies by opening up the risk of uncontrolled changes. Friction can be good to a software project. If I look at those benefits again, I could probably talk to why it&amp;rsquo;s good that you can&amp;rsquo;t do that or why it&amp;rsquo;s already possible without putting all your code in one repo. Let&amp;rsquo;s do it!&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Ease of code reuse&lt;/strong&gt; - It is easier if all the code out there is easily accessible, but is that really what you want? That seems like a very stack overflow copy paste solution to just access any solution provided in the monorepo. I&amp;rsquo;d rather build on a solid foundation, even at the cost of velocity. You make up for it when the worst happens because you have clearer boundaries to debug.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Simplified dependency management&lt;/strong&gt; - It is simpler to not have to think about versions, but is that better? Package dependencies are still required for third party tooling so you aren&amp;rsquo;t avoiding it and at best you are losing out on meaningful semantic versioning allowing you to rollback dependencies easily without having to manage all the commits of an entire repository. Additionally a good framework team can predict needs and work with you to best provide APIs that solve more than just your needs.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Atomic commits&lt;/strong&gt; - As I noted above, this is a benefit I can get behind, but I don&amp;rsquo;t know that it&amp;rsquo;s always what you want in a distributed set of apps and libraries. Friction is good and not everything needs to be hacked together in the same pull request. It can be fine to build the foundations in one library at a specific version, then add support in the app in another repo. I do understand that it&amp;rsquo;s hard when teams are working with fewer people than they need and there may not be anyone to own that library you need to contribute to.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Large-scale code refactoring&lt;/strong&gt; - If your project is so coupled that you need to do such large scale refactoring where this is valuable, I wonder if your problem is not the repo but the codebase. Code is a liability, not an asset. I&amp;rsquo;d rather have a heavily factored set of projects that have clear protocols and when one isn&amp;rsquo;t meeting needs, replace it. This sounds to me like everything is so interconnected that it&amp;rsquo;s hard to reason about, so you need to have it all in one folder so you can just unravel the spaghetti.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Collaboration across teams&lt;/strong&gt; - Multiple repos already provide this sort of collaboration in a more sustainable and arguably responsible way—you just contribute to their repository. I know I know, there is overhead in that in time and velocity, but really shouldn&amp;rsquo;t we be aiming for sustainability? I know in a monorepo you can still use code ownership to force a team to be required to review pull requests, but that&amp;rsquo;s not always great. One large pull request could end up requiring dozens of code reviews because you&amp;rsquo;ve touched multiple teams&amp;rsquo;s owned code. I know that&amp;rsquo;s the same with multiple repos, but that&amp;rsquo;s my point.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;I think there is an artificial feeling to this that makes me feel like it&amp;rsquo;s about perceived speed and velocity, not good software organization. In many of these cases it seems to aim to remove all barriers to any contribution instead of building good foundations.&lt;/p&gt;&#xA;&lt;p&gt;And I&amp;rsquo;m not even getting into the massively large size of these monorepos.&lt;/p&gt;&#xA;&lt;p&gt;So… with all that, I&amp;rsquo;m starting to come around on it to a point. I do think having &lt;em&gt;all&lt;/em&gt; applications in one repo is not worth the tradeoffs. However, I do understand why a large web app would want to be a monorepo with all libraries accessible in the repo—it&amp;rsquo;s all one stack. I think I also understand a platform as a monorepo as well, those scenarios where the application stack is a platform and apps are built on that platform.&lt;/p&gt;&#xA;&lt;p&gt;There is a certain reality to it that I understand. Teams and organizations change. Ownership and focus changes. When there is no team to own something, it can very quickly lead to a library or project getting duplicated instead of made a true dependency. These realities open my mind up to it a bit more.&lt;/p&gt;&#xA;&lt;p&gt;I read somewhere that &amp;ldquo;Understanding requires effortful engagement&amp;rdquo; and I think I just did this exercise here. I&amp;rsquo;m not convinced this is the best path for an organization, but I think I understand it more and maybe would choose to group more projects together—especially if they are all in the goal of shipping a single application in a single technology stack. What do you think? I&amp;rsquo;d love to continue thinking through this and if you have experiences that talk to monorepos, I&amp;rsquo;d love to hear. Let&amp;rsquo;s talk on &lt;a href=&#34;https://twitter.com/jnjosh&#34;&gt;twitter&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Things 3 as a Kanban board</title>
      <link>https://jnjosh.com/workshop/posts/things-as-kanban/</link>
      <pubDate>Sun, 04 Sep 2022 20:38:22 -0400</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/things-as-kanban/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve written from &lt;a href=&#34;https://jnjosh.com/posts/usingthings/&#34;&gt;time&lt;/a&gt; to &lt;a href=&#34;https://jnjosh.com/posts/productivity/&#34;&gt;time&lt;/a&gt; about productivity on this blog. I definitely think about todo lists and productivity too much and constantly try different things. Earlier this year I went back to paper even just to keep track of all the things I need to do in a day. Well… &lt;a href=&#34;https://jnjosh.com/microposts/2022-08-13-015726/&#34;&gt;starting a new job&lt;/a&gt; gave me another opportunity to evaluate how I track todos… and I&amp;rsquo;m back to using &lt;a href=&#34;https://culturedcode.com/things/&#34;&gt;Things 3&lt;/a&gt;, but in a new way!&lt;/p&gt;&#xA;&lt;p&gt;Simplicity was my first goal, I don&amp;rsquo;t want to have to keep tagging things so I simplified down to two tags, &amp;ldquo;In Progress&amp;rdquo; meaning I&amp;rsquo;m currently working on it as you&amp;rsquo;d expect and &amp;ldquo;Blocked&amp;rdquo; meaning I can&amp;rsquo;t work on it but need to keep it in focus, not just lost back to it&amp;rsquo;s project or area. This means that my today view has tasks I need to do today, but I can tag them with one of these two tags and remember where I am in the day. This benefits me personally in that I&amp;rsquo;m very visually distracted and can see another task and think &amp;ldquo;oh that&amp;rsquo;s more fun&amp;rdquo; or &amp;ldquo;oh that&amp;rsquo;s easier!&amp;rdquo;.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20220905/tags.png&#34; alt=&#34;Simpler Tags&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;This has another big benefit I&amp;rsquo;ve recently learned with Things. Things allows you to create multiple windows with a different view or set of filters selected. So I can keep a window for Today, and a window for In Progress open. I actually will often have 4 windows open, one with Inbox and one with Today&amp;rsquo;s Blocked tickets.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20220905/task.png&#34; alt=&#34;Multiple Windows&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;The neat thing here is that you can actually treat this as a sort of kanban-board style columns. Dragging a task from Today (filtered to No Tags) to Today (filtered with In Progress) will apply that tag appropriately. Dragging it back to the Today (with No Tags) window or to Blocked will remove tags or add the blocked tag, respectively. This has been really fun for me and requires very little work to maintain.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/20220905/drag.png&#34; alt=&#34;Dragging to In Progress&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;So I&amp;rsquo;ll dedicate one space, usually on my laptop monitor (my secondary monitor) to have this &amp;ldquo;kanban&amp;rdquo; setup. Then also have an In Progress window on my main window to keep the memory alive for what I&amp;rsquo;m currently trying to do. When I&amp;rsquo;m done, I go pick another thing and move it to In Progress.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s not perfect, filters will often get removed if there are no tickets in the current view with that tag, so you have to occasionally reset the windows. Also dragging from Blocked to In Progress just applies both tags. Still, it&amp;rsquo;s been pretty fun to use this way, especially on top of Things really great interface with hot keys and navigation. Let me know on &lt;a href=&#34;https://twitter.com/jnjosh&#34;&gt;twitter&lt;/a&gt; if you&amp;rsquo;ve done anything like this or have found any other tools that helped you.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Exploring Jetpack Compose</title>
      <link>https://jnjosh.com/workshop/posts/exploring-jetpack-compose/</link>
      <pubDate>Sun, 11 Jul 2021 18:32:27 +0000</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/exploring-jetpack-compose/</guid>
      <description>&lt;p&gt;After last month’s adventures into &lt;a href=&#34;https://jnjosh.com/posts/swift-time-capsule/&#34;&gt;SwiftUI with my little weather app&lt;/a&gt;, I thought it would be interesting to recreate the same project, but in Android&amp;rsquo;s upcoming version of a declarative UI framework, &lt;a href=&#34;https://developer.android.com/jetpack/compose&#34;&gt;Jetpack Compose&lt;/a&gt;. I&amp;rsquo;m curious how it compares to SwiftUI and what the Android development experience is like in 2021.&lt;/p&gt;&#xA;&lt;!-- more --&gt;&#xA;&lt;p&gt;I’ve been developing on Apple’s platforms in some form since the early aughts. With the iPhone SDK in 2008 I took it from a side curiosity to my main career. During my time at Two Toasters I started learning a little about Android, did &lt;a href=&#34;https://speakerdeck.com/jnjosh/cross-platform-shenanigans-part-2-android&#34;&gt;some Android work for our&lt;/a&gt; &lt;a href=&#34;https://jnjosh.com/posts/kotlinfromcpp/&#34;&gt;game engine at Highrise&lt;/a&gt;, and in 2018 I built an Android app for a client. I found that last experience painful—but I believe this was more about being more accustomed to Apple’s patterns and the nature of a side project.&lt;/p&gt;&#xA;&lt;p&gt;So… What’s it like now? Is this declarative UI as significant on Android as it seems to be on Apple platforms? I downloaded the arm64 Arctic Fox beta for my M1 MacBook Pro and decided to find out.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;  &lt;img src=&#34;https://jnjosh.com/uploads/2021/arctic.png&#34; /&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;Check it out on Github, &lt;a href=&#34;https://github.com/jnjosh/WeatherApp-Kotlin&#34;&gt;Kotlin Weather&lt;/a&gt; (&lt;a href=&#34;https://github.com/jnjosh/WeatherApp-Swift&#34;&gt;Swift Weather&lt;/a&gt; for reference)!&lt;/p&gt;&#xA;&lt;p&gt;First thing I noticed is this arm64 build of Android Studio runs pretty well for most of what I needed to do. The emulator itself launched almost immediately. Some of my most painful memories were dealing with launching the emulator—or even paying a lot for a third party emulator. Unfortunately, most of this was beta so I did have to do some shenanigans in Gradle to get all the right versions of compose and the tools I wanted to use. Xcode just includes all that you need to use the new tools without any extra configuration.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;  &lt;img src=&#34;https://jnjosh.com/uploads/2021/compare.png&#34; /&gt;&#xA;  &lt;figcaption&gt;Android Studio Arctic Fox Beta and Xcode 13 Beta Side by Side with their respective cell views&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;The big differences I noticed between SwiftUI and Jetpack Compose are mostly syntactical (&lt;code&gt;VStack&lt;/code&gt; = &lt;code&gt;Row&lt;/code&gt;, &lt;code&gt;HStack&lt;/code&gt; = &lt;code&gt;Column&lt;/code&gt;) but in some cases I think Android&amp;rsquo;s approach is harder to reason about. Now this could be a situation where I just didn&amp;rsquo;t understand (or it&amp;rsquo;s still just early). Still, thinking about State and LiveData types seems much more difficult with Compose.&lt;/p&gt;&#xA;&lt;p&gt;For example, in SwiftUI I can just identify my model as a &lt;code&gt;@StateObject&lt;/code&gt; and using &lt;code&gt;@Published&lt;/code&gt; I can receive updates.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;StateObject&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WeatherReportModel&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;WeatherReportModel&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ObservableObject&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;OpenWeatherMapServiceClient&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Published&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;currentWeather&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WeatherReportSectionModel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Published&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;forecasts&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;WeatherReportSectionModel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This state management can get complicated but a simple example is easily decorated with these property wrappers. I expected Kotlin and its much more expressive annotation system to be better for this kind of thing. Unfortunately, I found it harder to reason about. Eventually I got something working by observing a LiveData in my ViewModel as a Compose State type, but it wasn’t immediately clear what the best pattern here should be. I ended up looking at other sample projects to see what they did. This alone took me down some false starts as the beta-nature of this lead to implementations that aren’t available since last year.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Set up a mutable live data in my view model&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;weatherCellModel&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;MutableLiveData&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;WeatherCellModel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;weatherLiveData&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;LiveData&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;WeatherCellModel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;kr&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;weatherCellModel&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Then later, observe it as state (with null as default)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;report&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WeatherCellModel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;by&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;weatherLiveData&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;observeAsState&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;I’m going to keep reading as Compose continues to mature. I’m sure I’m overcomplicating it out of ignorance, but SwiftUI&amp;rsquo;s method for managing State feels easier to think about.&lt;/p&gt;&#xA;&lt;p&gt;Once you get all the state dealt with, most of the differences start becoming syntax. SwiftUI expects you to conform to their &lt;code&gt;View&lt;/code&gt; struct where Compose is built in a &lt;code&gt;@Composable&lt;/code&gt; decorated free function. Both systems use modifiers to change the way a component is displayed.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Jetpack Compose&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;n&#34;&gt;text&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;n&#34;&gt;fontWeight&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FontWeight&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ExtraBold&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;n&#34;&gt;fontSize&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;18.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// SwiftUI&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;font&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;headline&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fontWeight&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bold&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Interestingly modifiers in SwiftUI are like builders on the Text object where in Compose, they are parameters. This seems a little funny to me in that Java and Android seems more likely to use that builder pattern.&lt;/p&gt;&#xA;&lt;p&gt;One last thing that is nice, and similar to SwiftUI’s live view is how your JetPack Compose based functions can be updated and automatically displayed in the emulator. SwiftUI does this too but it’s built in to Xcode—and often is broken in my experience. Many times when working on this Kotlin-based weather app, I’d update layout and switch over to the emulator to find it already running my change.&lt;/p&gt;&#xA;&lt;h3 id=&#34;is-itthe-future&#34;&gt;Is it… the future!?&lt;/h3&gt;&#xA;&lt;p&gt;I think, yes. It reminds me a lot of early SwiftUI and I think seeing how Compose evolves will be fun to watch. I think it will be an important tool for Android development in the future. SwiftUI felt more “ergonomic” to me but that is admittedly biased by my comfort with Apple platforms—not to mention it has had a year or so more to evolve. I guess next I’ll have to build this on React to really see all three major platforms.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Building My Second Brain</title>
      <link>https://jnjosh.com/workshop/posts/building-my-second-brain/</link>
      <pubDate>Sun, 04 Jul 2021 00:12:57 +0000</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/building-my-second-brain/</guid>
      <description>&lt;p&gt;You may have noticed lately that I’ve been blogging a bit more regularly. It’s something I’ve always wanted to do with my site but always found reasons not to do it. A lot of times the reasons were just being busy—which is kind of a fake reason. Other times it was I just didn’t know what I wanted to write about. So I’d like to talk a little about what changed in my blogging mindset, what tools I’ve been using, and how I’ve started building a second brain.&lt;/p&gt;&#xA;&lt;!-- more --&gt;&#xA;&lt;h3 id=&#34;what-is-a-second-brain&#34;&gt;What is a Second Brain?&lt;/h3&gt;&#xA;&lt;p&gt;I think way &lt;a href=&#34;https://jnjosh.com/categories/productivity/&#34;&gt;too much about productivity tools&lt;/a&gt;. I&amp;rsquo;m always trying new things and reevaluating what I use. Sometimes it&amp;rsquo;s a distraction, but it&amp;rsquo;s often led to new systems that help me get things done. During one of these late night &lt;a href=&#34;https://www.sleepfoundation.org/sleep-hygiene/revenge-bedtime-procrastination&#34;&gt;revenge bedtime procrastination&lt;/a&gt; sessions I came across this idea of the &lt;a href=&#34;https://www.youtube.com/watch?v=--x10rQ4FvI&#34;&gt;Second Brain&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The core principle here is a system around the notes you take from books, articles, and videos such that you don’t have to maintain these ideas in your head. By capturing your notes into a system like &lt;a href=&#34;http://roamresearch.com&#34;&gt;Roam Research&lt;/a&gt;, &lt;a href=&#34;https://obsidian.md&#34;&gt;Obsidian&lt;/a&gt;, or &lt;a href=&#34;https://www.craft.do&#34;&gt;Craft&lt;/a&gt; you gain the ability focus on connections and building on existing knowledge. All these tools allow you to see connections via backlinks (or in some cases visual graphs).&lt;/p&gt;&#xA;&lt;p&gt;Next change is how to take notes—which for me was previously in the category of &amp;ldquo;not good”. I first read about &lt;a href=&#34;https://notes.andymatuschak.org/Evergreen_notes&#34;&gt;Andy Matuschak’s Evergreen notes&lt;/a&gt;. This showed me that taking notes isn&amp;rsquo;t the point, thinking is. To me that means categorization and tagging is less important than the note itself. Notes that should be atomic units. I&amp;rsquo;ve recently started reading &lt;a href=&#34;https://www.amazon.com/How-Take-Smart-Notes-Nonfiction-ebook/dp/B06WVYW33Y&#34;&gt;&amp;ldquo;How to Take Smart Notes&amp;rdquo;&lt;/a&gt; which goes into similar ideas with the &lt;a href=&#34;https://en.wikipedia.org/wiki/Zettelkasten&#34;&gt;Zettelkasten System&lt;/a&gt;. These notes allow me to think, they allow me to see connections, they gave me the realization that writing is thinking.&lt;/p&gt;&#xA;&lt;p&gt;The Zettelkasten System talks about three types of notes. Fleeting notes are singular thoughts you have that come up at any time, permanent notes are those that have been formally written with connections to other notes, and project notes are those notes written about a specific project. It also uses notes that are literature references and slip boxes (Zettelkasten is German for “slip of paper box”).&lt;/p&gt;&#xA;&lt;p&gt;Let’s look at what I’m currently doing.&lt;/p&gt;&#xA;&lt;h3 id=&#34;how-im-taking-notes-and-creating-more&#34;&gt;How I&amp;rsquo;m Taking Notes and Creating More&lt;/h3&gt;&#xA;&lt;p&gt;Of the tools I mentioned above, I’ve been using Craft (it’s a beautiful and native app) for capturing these notes and connections. I’m also using &lt;a href=&#34;https://culturedcode.com/things/&#34;&gt;Things 3&lt;/a&gt; as a system for both capturing new ideas and planning tasks I want to do.&lt;/p&gt;&#xA;&lt;p&gt;I start with something like fleeting notes, if something comes up during the day I’ll add a note in my Things 3 inbox to think more about it later. If I decide to turn it into a note, I’ll move it into Craft’s inbox and within the next week, I’ll migrate it to a permanent note in my “Note Box” folder. Here I try to be brutal about the ideas and only add them if they either expand some knowledge I have notes for or something I want to build on. The point isn’t to have hundreds of notes I never see but to build on what I know or what I want to know.&lt;/p&gt;&#xA;&lt;p&gt;Next when reading books, articles, or videos I create a document in Craft’s inbox that is a sort of bulleted feed of things that surprised me or caused me to think. If a thought is something I want to capture, I’ll make it a fleeting note in the inbox as well—but link to it in the document. When finished consuming the content I move it to an “Input” folder in Craft. Understanding requires effortful engagement and this engagement has had me focusing on what I want to capture from an article.&lt;/p&gt;&#xA;&lt;p&gt;Finally when I think I have enough about some topic to talk about I’ll schedule a blog post in my Things area for my blog—I try to plan for the next 3–5 posts so I have time to write code if needed. This engagement in these ideas have given me more ideas about what I want to write about and kept me excited about learning more about the topic such that I can create something interesting. Then I write my blog post in Craft itself, linking to all the notes that informed it and storing it in an “Output” folder.&lt;/p&gt;&#xA;&lt;h3 id=&#34;will-it-keep-working&#34;&gt;Will it Keep Working?&lt;/h3&gt;&#xA;&lt;p&gt;I don’t know! My track record here isn’t great—this could be my last blog post until next year when I’m reminded that I want to blog again. I hope not and this system has given me the best framework to organize my thinking, plan for what I want to learn, and start to create more. I’m still learning about good note taking, Zettelkasten, and second brain. Let me know on &lt;a href=&#34;https://twitter.com/jnjosh&#34;&gt;twitter&lt;/a&gt; if you&amp;rsquo;ve done anything like this or have found any other tools that helped you.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Designing Interviews for Engineers</title>
      <link>https://jnjosh.com/workshop/posts/designing-interviews-for-engineers/</link>
      <pubDate>Sun, 27 Jun 2021 18:44:58 +0000</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/designing-interviews-for-engineers/</guid>
      <description>&lt;p&gt;Interviewing is hard and there are a lot of problems in the industry and the processes that we use. A lot of companies rely on a half to full day interview cycle to both get an idea of the candidate’s skills/fit and give the candidate an idea of what your team is about. 4–5 hours is hard to give both employer and candidate a complete picture of what it’s like to work on this team.&lt;/p&gt;&#xA;&lt;p&gt;I think about this too much probably and I think we as an industry can do better at hiring engineers. When I was at Highrise I worked a lot to help &lt;a href=&#34;https://jnjosh.com/posts/onhiringathighrise/&#34;&gt;rebuild our system for hiring&lt;/a&gt; and as a hiring manager in my current role, I&amp;rsquo;m continuing to think how to best do this.&lt;/p&gt;&#xA;&lt;p&gt;I think we all know the answer in a perfect world. &lt;em&gt;Design the interview for each candidate&lt;/em&gt;. This is hard of course because this kind of due diligence in hiring takes time and time is the thing hiring managers often lack.&lt;/p&gt;&#xA;&lt;p&gt;I think we can have this though. I think we can build a system that allows us to do this due diligence, but also is a system we can rely on to design it in our busy schedules. My current thinking on interviewing has grown from two key tenets.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;It&amp;rsquo;s the hiring manager&amp;rsquo;s job to guide and design an interview for each candidate.&lt;/li&gt;&#xA;&lt;li&gt;There should be a source of truth that anyone on an interview team can use to know the background of the candidate, the plan for the interview, and the expectations for each interviewer.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Let’s dive into this a bit more.&lt;/p&gt;&#xA;&lt;h3 id=&#34;its-the-hiring-managers-job-to-design-the-interview&#34;&gt;It&amp;rsquo;s the hiring manager&amp;rsquo;s job to design the interview&lt;/h3&gt;&#xA;&lt;p&gt;As former engineers, we engineering managers want to build systems that can be easily relied on. Unfortunately, this leads to many hiring managers applying the same interview to every candidate as if it’s a function where the input is the new candidate and the output is a true/false decision on if they “fit” and have the skills. It often goes something like: let’s have the candidate do several &lt;a href=&#34;https://leetcode.com&#34;&gt;leetcode&lt;/a&gt; challenges, give them a few trick or clever questions, and use our guts to determine if they are a good candidate.&lt;/p&gt;&#xA;&lt;p&gt;This is very subjective and it doesn’t really take into account that people have different backgrounds and experiences.&lt;/p&gt;&#xA;&lt;p&gt;People can be anxious or just distracted in an interview. Building a system that tests them on areas that aren’t what they would do from day to day and trying to trick them or test them under stress leads to false results. In fact this just builds on those anxieties.&lt;/p&gt;&#xA;&lt;p&gt;Instead let’s try to do better. Let’s try to use what we know about the candidate to design an interview for them that best answers the questions of “should we hire them?” and “what’s it like to work with them?”. You or someone on your team has likely done an initial screening call with them. Assuming you want to move to the next step, ask questions about what you know from that call or from their resume.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;They were really strong with architecture but didn’t really talk much about testing. Are they a good tester?&lt;/li&gt;&#xA;&lt;li&gt;They had some really good fundamentals but their designs were not clear or too clever. How would they build an architecture with your business’ challenges?&lt;/li&gt;&#xA;&lt;li&gt;They were heads down and finished every challenge during the screening but they didn’t talk at all. How would they communicate with the rest of the team or non-technical stakeholders?&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;These questions can inform what to do next. They help you design the best interview for the candidate. Here’s where you can start relying on systems. Build yourself a library of projects that can be sorted by “focus area” and “level”. Then add some common questions for each project and what you’d expect to see from candidates when given the project. This takes work but it can be reused for a long time.&lt;/p&gt;&#xA;&lt;p&gt;These projects should be specific to the type of work your team does—think about having the candidate build a UI with a similar spec to what your team uses instead of general algorithm questions. You can bake the algorithm problem into the project’s goals of course, but it alone is not the point. Then for each project give it a general focus area and level.&lt;/p&gt;&#xA;&lt;p&gt;The best projects are more closely related to your business, but some vague examples could be:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Build an image app that downloads results from flickr and displays them.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Focus Area UIKit, Networking, API&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Level: Software Engineer II–Senior Engineer&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Common Questions and Notes:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Do they properly separate concerns between API functionality and UI?&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Do they communicate about how they want to build it and work with you&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;  or just go heads down?&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; If they breeze through this section move on to either unit testing &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;  or more architectural questions about their implementation.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Implement a framework for providing autocomplete search results &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  from the Github API&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Focus Area: Architecture, API Design&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Level: Senior Engineer–Staff Engineer&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Common Questions and Notes:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; How did they approach their access to components? &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;-&lt;/span&gt; Is their design a pass through to the API or something more modeled &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;  to the problem? How would they improve it?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;You can prep projects to help get them started—you only have an hour and seeing people set up a project isn’t always the most valuable use of time. Then share it with them at the beginning of the interview or earlier if your company utilizes take home projects. All of these also give you the opportunity to see how they work with the tools you use daily.&lt;/p&gt;&#xA;&lt;p&gt;Building a library like this gives you many different focus areas that you can plug in to best determine this candidate’s skill level, communication level, and what it’s like to work with them. Further, by designing this for each candidate you have fewer chances of getting to a debrief and not knowing if you should hire or not.&lt;/p&gt;&#xA;&lt;p&gt;This does take some work to set up but once you have a basic process it’s really not a heavy lift. In my experience, I’ll review all the notes for a candidate and come up with a basic interview panel in as little as 10–15 minutes. You are responsible for bringing this individual on to your team and the cost of making bad decisions is high, it’s worth taking at least a little time to think through each candidate. You are the &lt;em&gt;hiring&lt;/em&gt; manager.&lt;/p&gt;&#xA;&lt;h3 id=&#34;source-of-truth-for-the-interview-plan&#34;&gt;Source of Truth for the Interview Plan&lt;/h3&gt;&#xA;&lt;p&gt;Now that you have built a plan for this new candidate, you need to make sure everyone on your team understands it and knows what to expect. Relying on everyone to just decide what to do during the interview leads to unanswered questions and duplicated work—which just makes you look like not a good company to join.&lt;/p&gt;&#xA;&lt;p&gt;To avoid this I’ve started creating what I call a “prebrief” document. This is a simple document that:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Links to all information about the candidate—their resume, previous notes from screenings, your company’s internal expectations for the relevant career level, or any other relavant data about the role or candidate.&lt;/li&gt;&#xA;&lt;li&gt;I’m mixed on this section, but I like to include a “tl;dr” section of the above information that summarizes the data you’ve seen so far. Some care needs to be taken here to be void of any opinions—keep it focused on the data and don’t accidentally give people your opinions on the candidate. Your team is also busy so giving them a brief they can quickly glance at helps everyone be more prepared.&lt;/li&gt;&#xA;&lt;li&gt;A brief schedule for each person on the panel with their focus area and highlights to look out for.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;A Sample Prebrief Document&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/uploads/2021/hasslehoff.png&#34; alt=&#34;hasslehoff.png&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;Now set up a brief call with your team—can be as little as 15 minutes—to go over each person’s role in the panel and aim for clarity. Each person should know what they are looking for in the interview and can give the candidate a quick agenda of what to expect.&lt;/p&gt;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;These two tenets have helped me build better interviews on my teams from start-ups to large businesses. It’s helped me better communicate with all the people I’m expecting to help me decide on hiring and the added clarity creates a better environment for candidates—they are told what to expect, have interviews that actually challenge them where they are, and they don’t have the same question 3 times.&lt;/p&gt;&#xA;&lt;p&gt;What do you think? Let me know on &lt;a href=&#34;https://twitter.com/jnjosh&#34;&gt;twitter&lt;/a&gt;. I&amp;rsquo;m constantly interested in learning how other people solve these problems so I&amp;rsquo;d love to hear what your team does.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>A Swift Time Capsule</title>
      <link>https://jnjosh.com/workshop/posts/swift-time-capsule/</link>
      <pubDate>Sun, 20 Jun 2021 23:47:11 +0000</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/swift-time-capsule/</guid>
      <description>&lt;p&gt;A while back I got into an argument with a friend about the lifetime of code “now days”. He argued that code written in C and C++ was much more likely to continue working forever, needing fewer updates, and being generally more of an asset. My response was generally “code is a liability, not an asset”. We should strive to create good code, but too much changes in the industry year to year and you’ll eventually need to replace it with new tools and environments—at least for most consumer facing apps.&lt;/p&gt;&#xA;&lt;p&gt;We are 7 years into this new Swift adventure—which kind of proves my point above, we should be migrating from Objective-C to Swift as it’s better in almost every way. That hurts a lot to say but it’s true. However, I’m wondering if my friend was right to some extent. Swift today is not Swift of 2014 and had you been an early adopter, you would have had a lot of migrating to do.&lt;/p&gt;&#xA;&lt;p&gt;I was curious lately how much has changed since the beginning and &lt;a href=&#34;https://github.com/jnjosh/WeatherApp-Swift&#34;&gt;found a project&lt;/a&gt; I had worked on back in 2014—with &lt;a href=&#34;https://jnjosh.com/posts/swift-you-slash-ve-changed-so-much/&#34;&gt;a small update for Swift 1.2 in 2015&lt;/a&gt;. The project is to basically consume weather from a public API and display it—a super small representation of what most apps I’ve worked on contain. I downloaded a fresh copy of Xcode 13 and opened it up! So how did it change?&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/jnjosh/WeatherApp-Swift&#34;&gt;Check it out, Swift Weather&lt;/a&gt;!&lt;/p&gt;&#xA;&lt;h2 id=&#34;opening-the-time-capsule&#34;&gt;Opening the Time Capsule&lt;/h2&gt;&#xA;&lt;p&gt;Of course, I was immediately prompted with “WeatherApp contains source code developed with Swift 3.x” and I’d need to migrate to Swift 4. Of course I was opening a Swift 1.2 project and have no interest in stopping at Swift 4, so let’s continue.&lt;/p&gt;&#xA;&lt;p&gt;Oh… well I guess I can’t really go on. FIrst thing is obviously this app could not run as it targeted iOS 8 and iOS 9 is as far back as Xcode 13 cares to think about. However, the real problem was I decided to use a custom JSON parsing system (this was all before &lt;code&gt;Codable&lt;/code&gt;!). &lt;a href=&#34;https://github.com/thoughtbot/Runes&#34;&gt;Runes&lt;/a&gt; and &lt;a href=&#34;https://github.com/thoughtbot/Argo&#34;&gt;Argo&lt;/a&gt; were used with this incredibly expressive syntax that I did not completely forget about. /sarcasm.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;JSONValue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Forecast&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Forecast&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;&amp;lt;^&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;json&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;||?&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;list&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;So I didn’t really want to unravel that mess. Since most the UI stuff would be needing a rewrite in SwiftUI and I now have access to &lt;code&gt;Codable&lt;/code&gt; I thought I&amp;rsquo;d just start from scratch on this project.&lt;/p&gt;&#xA;&lt;p&gt;Before continuing, one feature of early Swift that I thought was interesting was &lt;a href=&#34;https://en.wikipedia.org/wiki/Currying&#34;&gt;method currying&lt;/a&gt;. This allowed you to treat a function with multiple arguments as a sequence of functions. It was removed in (I think) Swift 3? I still think it&amp;rsquo;s neat, but it&amp;rsquo;s probably for the best to remove it.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;kelvin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Double&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WeatherItem&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WeatherItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;kelvin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;kelvin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;h2 id=&#34;a-whole-new-world&#34;&gt;A Whole New World&lt;/h2&gt;&#xA;&lt;p&gt;I deleted everything and just started from scratch. I knew that the API and Parsing side of this would be rewritten with &lt;code&gt;URLSession&lt;/code&gt; and &lt;code&gt;Codable&lt;/code&gt; structs and I’d want to build a new UI with SwiftUI—something I’ve had little time to work with over the last couple years.&lt;/p&gt;&#xA;&lt;p&gt;The first thing I noticed? A new App Project is very simple. It contains the project, a &lt;code&gt;WeatherApp.swift&lt;/code&gt;, an Assets bundle, and a &lt;code&gt;ContentView.swift&lt;/code&gt;. It appears the Info.plist has been moved to be shown as part of the project.&lt;/p&gt;&#xA;&lt;p&gt;Building the new API stuff was much better, &lt;code&gt;Codable&lt;/code&gt; is a nice system. The real update was switching from blocks to &lt;a href=&#34;https://developer.apple.com/videos/play/wwdc2021/10132/&#34;&gt;async functions available with Swift 5.5&lt;/a&gt;. That turned my old Objective-C era mindset &lt;code&gt;fetchWeather&lt;/code&gt; function into a much easier function to reason about. It went from this…&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;fetchWeatherWithLatitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;latitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Double&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;longitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Double&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;WeatherItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Void&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;URLString&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;baseURLString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stringByAppendingFormat&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;weatherPathFormat&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;latitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;longitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;URL&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSURL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;URLString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;request&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSURLRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;URL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;URL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;NSURLConnection&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sendAsynchronousRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;queue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;queue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;completionHandler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSURLResponse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSData&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Void&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;jsonError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;AnyObject&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSJSONSerialization&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;JSONObjectWithData&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSJSONReadingOptions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;jsonError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;dispatch_async&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dispatch_get_main_queue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;JSONValue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;parse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;item&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WeatherItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;item&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;… into something much more clear.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;weather&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;latitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Double&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;longitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Double&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WeatherReport&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;request&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;weatherRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;forEndpoint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;current&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;latitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;latitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;longitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;longitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;apiKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;apiKey&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;URLSession&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;shared&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;guard&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;report&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;JSONDecoder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;WeatherReport&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;throw&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WeatherServiceError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;decodingFailure&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;report&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Now I can leave behind not only all the pre-&lt;code&gt;URLSession&lt;/code&gt; and pre-&lt;code&gt;Codable&lt;/code&gt; world, but more importantly I can leave behind the strange code flow we all lived with under the call-callback structure.&lt;/p&gt;&#xA;&lt;p&gt;The other big thing is of course, SwiftUI. It took me a little bit to think through how to structure things with a pure-SwiftUI project—and I probably have a lot to learn still—but it’s so much more easy to read and reason about than the older style. I learned to read that through experience, SwiftUI code I could probably show non-tech people and they could read it. Also it simplifies things a lot.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ContentView&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;View&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;StateObject&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;WeatherReportModel&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;updateWeatherResults&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fetchAllReports&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;location&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CLLocationCoordinate2D&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;latitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;35.9940&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;longitude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;78.8986&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;some&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;View&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;NavigationView&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;List&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;weatherReport&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;currentWeather&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;WeatherForecastSectionView&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;forecast&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;weatherReport&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;isCurrent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;ForEach&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;forecasts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;forecast&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;WeatherForecastSectionView&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;n&#34;&gt;forecast&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;forecast&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;navigationTitle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Swift Weather&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;listStyle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sidebar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;refreshable&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;updateWeatherResults&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;task&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;updateWeatherResults&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;That last bit there with &lt;code&gt;.refreshable { … }&lt;/code&gt; is a new feature in SwiftUI that&amp;rsquo;ll automatically add a &lt;code&gt;UIRefreshControl&lt;/code&gt; to your UI. So nice.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;In the end, it’s not less code—actually CLOC reports it’s about the same amount of code (I did format a lot of multiline inits, though). The interesting thing is not that the lines of code are smaller however, but that the code is more easy to reason about and read. At least I hope so. I’ll have to put it back in the time capsule and come back in 2028 to see how things worked out.&lt;/p&gt;&#xA;&lt;p&gt;I still think code is not something you should treat like an asset. You should do your best with what you know but be ready to move forward. This little experiment showed me that moving forward can be good, &lt;code&gt;Codable&lt;/code&gt; and async/await alone show this.&lt;/p&gt;&#xA;&lt;p&gt;What do you think? Let me know on &lt;a href=&#34;https://twitter.com/jnjosh&#34;&gt;twitter&lt;/a&gt; or feel free to look through the &lt;a href=&#34;https://github.com/jnjosh/WeatherApp-Swift&#34;&gt;WeatherApp sample project&lt;/a&gt; and play around with it.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Patterns with Swift Concurrency in new APIs</title>
      <link>https://jnjosh.com/workshop/posts/patterns-in-swift-concurrency/</link>
      <pubDate>Sun, 13 Jun 2021 21:39:04 -0400</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/patterns-in-swift-concurrency/</guid>
      <description>&lt;p&gt;I’ve watched a lot of this year’s &lt;a href=&#34;https://developer.apple.com/wwdc/&#34;&gt;WWDC&lt;/a&gt; sessions—like over 30 this weekend. This year just seemed to be really interesting and I&amp;rsquo;ve really enjoyed many of the videos. Also, I&amp;rsquo;m super excited about DocC and can&amp;rsquo;t wait to start building better documentation!&lt;/p&gt;&#xA;&lt;p&gt;One thing has been bugging me. After watching &lt;a href=&#34;https://developer.apple.com/wwdc21/10132&#34;&gt;Meet async/await in Swift&lt;/a&gt;,  &lt;a href=&#34;https://developer.apple.com/wwdc21/10132&#34;&gt;Meet MusicKit for Swift&lt;/a&gt; and &lt;a href=&#34;https://developer.apple.com/videos/play/wwdc2021/10114/&#34;&gt;Meet StoreKit 2&lt;/a&gt; sessions I started noticing an interesting difference in patterns in use with Swift Concurrency. So far I’ve seen 3 different patterns.&lt;/p&gt;&#xA;&lt;h3 id=&#34;pattern-1-the-ol-fetch-me-a-response-with-this-request-pattern&#34;&gt;Pattern 1: The ol’ fetch me a response with this request pattern&lt;/h3&gt;&#xA;&lt;p&gt;This seems the most common pattern I’ve seen and what I’d probably naturally do when building something that supports the new language features.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;URLSession&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;shared&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Passing a request to a data task will eventually result in a response. This seems the most abstracted where a request describes the request itself but the data task asynchronously fetches the data and responds.&lt;/p&gt;&#xA;&lt;h3 id=&#34;pattern-2-the-request-gives-you-the-response-pattern&#34;&gt;Pattern 2: The request gives you the response pattern&lt;/h3&gt;&#xA;&lt;p&gt;This was the first time I had the thought that the patterns were different. While watching the &lt;a href=&#34;https://developer.apple.com/videos/play/wwdc2021/10294/&#34;&gt;Meet MusicKit for Swift&lt;/a&gt; session they reviewed the new API for &lt;a href=&#34;https://developer.apple.com/documentation/musickit/musiccatalogsearchrequest&#34;&gt;MusicCatalogSearchRequest&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;MusicCatalogSearchResponse&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;So when using this you’d be asking the request itself to asynchronously fetch a response.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;album&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;albumRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This seems to blur the lines of what responsibilities requests have. Typically I wouldn&amp;rsquo;t think of a request being the facilitator of it&amp;rsquo;s own response.&lt;/p&gt;&#xA;&lt;h3 id=&#34;pattern-3-the-static-function-request&#34;&gt;Pattern 3: The static function request&lt;/h3&gt;&#xA;&lt;p&gt;Later while watching the &lt;a href=&#34;https://developer.apple.com/videos/play/wwdc2021/10114/&#34;&gt;Meet StoreKit 2&lt;/a&gt; session I noticed another pattern, request directly associated as a static method on &lt;a href=&#34;https://developer.apple.com/documentation/storekit/product/&#34;&gt;Product&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;identifiers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Leading to a fetch of products like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;products&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Product&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;identifiers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;In my limited experience with Ruby on Rails, this reminds me of the patterns there for some reason. The type itself knows how to find instances of itself given some search criteria. This probably reads the nicest and is maybe the most ergonomic but it means a lot of the fetching behavior is owned by the Product type itself or an extension.&lt;/p&gt;&#xA;&lt;h3 id=&#34;sowhats-the-point&#34;&gt;So… what’s the point?&lt;/h3&gt;&#xA;&lt;p&gt;I find it interesting that with this new push for Swift Concurrency there isn&amp;rsquo;t a clear pattern that Apple recommends. Apple has often had strange APIs that come out of nowhere in the past—I&amp;rsquo;m looking at you &lt;code&gt;CAMediaTimingFunction&lt;/code&gt;&amp;rsquo;s &lt;code&gt;initWithControlPoints::::&lt;/code&gt;. However, this seems to be a bit more fundamental. Maybe it&amp;rsquo;s an artifact of many teams doing different things then all trying to get something ready for Swift Concurrency? Maybe it really doesn&amp;rsquo;t matter and I&amp;rsquo;m over thinking it? Maybe there are good reasons I&amp;rsquo;m not seeing in the public interface for these different APIs to use different patterns? I don&amp;rsquo;t know, but I’m interested to see what the community adopts, if anything.&lt;/p&gt;&#xA;&lt;h3 id=&#34;what-is-a-good-pattern&#34;&gt;What is a good pattern?&lt;/h3&gt;&#xA;&lt;p&gt;Patterns are mostly important as a means of consistency and convention, so I&amp;rsquo;m not going to over index on what is right. It&amp;rsquo;s way too easy to look at a public interface and judge it harshly.&lt;/p&gt;&#xA;&lt;p&gt;For me, that initial pattern as shown in the &lt;code&gt;URLSession&lt;/code&gt; example makes the most sense. Here you have a clear request type that can declare the request’s parameters and potentially be reusable, a response type, and a task type that owns the actual async work.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Saying Goodbye to Triangle Cocoa</title>
      <link>https://jnjosh.com/workshop/posts/goodbye-triangle-cocoa/</link>
      <pubDate>Sun, 03 May 2020 15:28:26 -0400</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/goodbye-triangle-cocoa/</guid>
      <description>&lt;p&gt;Organizing &lt;strong&gt;Triangle Cocoa&lt;/strong&gt;—CocoaHeads and NSCoder Nights—changed my life.&#xA;My career was mostly focused on .NET programming for AutoCAD and I dabbled with&#xA;Mac programming on the side. When the iPhone SDK was released I fell in love&#xA;with it. I started learning as soon as it was possible—I went from dabbling to&#xA;&amp;ldquo;taking it seriously&amp;rdquo;. There was a lot struggle in learning alone, none of my&#xA;coworkers knew anything about Apple platforms and there wasn&amp;rsquo;t as much content&#xA;out there as there is today.&lt;/p&gt;&#xA;&lt;p&gt;Eleven years later–my career has changed, I&amp;rsquo;m a better engineer, and I&amp;rsquo;ve found&#xA;a network of friends and mentors. Not to mention I&amp;rsquo;ve been a speaker at several&#xA;conferences and even other meetups. After all this time, &lt;strong&gt;I&amp;rsquo;ve decided to move on&#xA;as the organizer&lt;/strong&gt; and find someone else to guide the group&amp;rsquo;s future.&lt;/p&gt;&#xA;&lt;p&gt;Early on the struggle to understand Cocoa Touch with a few hours on nights and&#xA;weekends was a challenge. I started listening to other developers in the&#xA;industry via podcasting, twitter, and other local meetups. After attending&#xA;&lt;a href=&#34;https://gist.github.com/heckj/297037&#34;&gt;NSConf in Atlanta&lt;/a&gt; in early 2010, I was convinced I wanted to start a CocoaHeads&#xA;chapter (so we could talk about Mac dev too!). Throughout 2010 I tried several&#xA;times to get it moving and had little success. Adding it to &lt;a href=&#34;https://meetup.com&#34;&gt;meetup.com&lt;/a&gt;&#xA;in early 2011 helped it really start to grow.&lt;/p&gt;&#xA;&lt;p&gt;Over the years we&amp;rsquo;ve had so many great meetings—check out the &lt;a href=&#34;https://github.com/cocoaheads/raleigh&#34;&gt;historical repo&#xA;that lists all the CocoaHeads meetings and their talks&lt;/a&gt;. We&amp;rsquo;ve also had an&#xA;NSCoder Night going for most of this time too. Over &lt;strong&gt;800&lt;/strong&gt; members and &lt;strong&gt;726&lt;/strong&gt; events&#xA;in 10 years! In this time we&amp;rsquo;ve had meetings with as many as 60 people, a &lt;a href=&#34;https://vimeo.com/54730335&#34;&gt;talk&#xA;from the founder of CocoaHeads&lt;/a&gt;, and once as a group were able to outvote&#xA;other, bigger, cities to draw CocoaConf to Raleigh.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;  &lt;img src=&#34;https://jnjosh.com/assets/images/cocoaheads.jpeg&#34; /&gt;&#xA;  &lt;figcaption&gt;The biggest CocoaHeads we&#39;ve ever had. We had over 60 people that month and we had a talk from the founder of CocoaHeads.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;&lt;em&gt;So why stop?&lt;/em&gt; Early on information about Cocoa was not as wide-spread. I had such&#xA;a great time at that conference I asked myself, &amp;ldquo;what if we had a conference&#xA;every month?&amp;rdquo;. This &amp;ldquo;vision&amp;rdquo; of what CocoaHeads could be was a driving force in&#xA;making sure there was content for beginners and advanced engineers. I really&#xA;wanted to create a community that welcomed everyone. I think I succeeded.&#xA;However, nowadays there is so much content out there online I&amp;rsquo;ve struggled with&#xA;what my driving &amp;ldquo;vision&amp;rdquo; for the meetup would be. The answer has been, &amp;ldquo;I don&amp;rsquo;t&#xA;know&amp;rdquo;. I had to ask myself a new question, &amp;ldquo;Should someone else take ownership&#xA;of this meetup?&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;The answer to that question is &amp;ldquo;Yes&amp;rdquo;. I&amp;rsquo;ve done this for over 10 years&#xA;and it&amp;rsquo;s time for someone else to find their vision—assuming someone will take&#xA;it over. I emailed the group and heard nothing besides a lot of much appreciated&#xA;&amp;ldquo;thank you&amp;quot;s from many of my friends. Then surprisingly at the 11th hour, someone&#xA;decided to step up and take the meeting. I hope they can continue it and hope&#xA;for the best.&lt;/p&gt;&#xA;&lt;p&gt;Thank you to everyone who took the time to help organize the meetings, speak,&#xA;help others, and everyone who just attended meetings. You all made this one of&#xA;the best groups I&amp;rsquo;ve ever attended. You&amp;rsquo;ve also helped make this a great&#xA;community that continues with or without the meetup.&lt;/p&gt;&#xA;&lt;p&gt;Triangle Cocoa changed my life for the better. If you are considering organizing&#xA;a meetup in your community I highly recommend it. You learn so much about how to&#xA;be a leader, how to be a better contributor, and how to communicate with people.&#xA;You also will make a lot of new friends.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Another System for Productivity</title>
      <link>https://jnjosh.com/workshop/posts/productivity/</link>
      <pubDate>Sun, 29 Sep 2019 16:47:34 -0400</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/productivity/</guid>
      <description>&lt;p&gt;It‘s been at least &lt;a href=&#34;https://jnjosh.com/posts/list-fails/&#34;&gt;two years since I last wrote about TODO lists&lt;/a&gt; and I‘ve tried several different options from going back to the &lt;a href=&#34;https://bulletjournal.com&#34;&gt;Bullet Journal&lt;/a&gt;, trying &lt;a href=&#34;https://www.omnigroup.com/omnifocus&#34;&gt;OmniFocus&lt;/a&gt; again, to using &lt;a href=&#34;https://agenda.com&#34;&gt;Agenda&lt;/a&gt; for notes and todos. Through all this I still wasn&amp;rsquo;t feeling like I found a great system and easily could convince myself to ignore a todo or have to do weird things like moving tasks when I couldn&amp;rsquo;t get to them in a day. I &lt;em&gt;think&lt;/em&gt; I&amp;rsquo;ve found a new system for the long term.&lt;/p&gt;&#xA;&lt;!-- more --&gt;&#xA;&lt;p&gt;I &lt;a href=&#34;https://jnjosh.com/posts/usingthings/&#34;&gt;wrote about Things&lt;/a&gt; last year and it‘s no surprise to me that I went back to it. I love OmniFocus too but it‘s largely overkill for what I need and required too much management. Things is simple and powerful enough to do almost everything I want and provides a cool visualization to show the progress of a project.&lt;/p&gt;&#xA;&lt;p&gt;It‘s not &lt;a href=&#34;https://culturedcode.com/things/&#34;&gt;Things 3&lt;/a&gt; alone that is a part of this new system. I learned a new way to prioritize tasks for myself such that I can make good decisions about what I should be doing. It starts with a mindmap. Every week or just occasionally, I‘ll have a brain dump to get as many tasks down on paper. In my case I use &lt;a href=&#34;https://www.gingerlabs.com&#34;&gt;Notability&lt;/a&gt; on my iPad Pro and write out a basic mind map of tasks.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/productivity/mindmap-1.png&#34; alt=&#34;Initial Mind Map&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;It actually really helps me to do it in this format because I enjoy the writing—and drawing. This format also really helps me expand tasks that are related and expand out the map to more tasks that I might have forgotten.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/productivity/mindmap-2.png&#34; alt=&#34;Expanded Mind Map&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;Now that I have all these things listed out the next step is to prioritize. I‘d known about the four quadrant method for years—I think it‘s called the Eisenhower method. Recently I learned about a 9-space grid. It&amp;rsquo;s effectively the same idea though. On one axis the urgency increases, and on the other axis value. For example the top-left quadrant contains items that are high value and very urgent. I still end up using the 4 quadrant method but I think seeing it in context of the 9 grid is really helpful.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/productivity/prioritizing.png&#34; alt=&#34;Priorities&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;Next I‘ll do my best to prioritize everything on the mind map. This helps me think about relationships or dependencies between tasks and think about both how valuable/urgent they are. Sometimes it helps writing it into the 9-grid but I&amp;rsquo;ve found over time—and my use of the 4-grid instead—I can just as easily rate everything on a 1-4 scale.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/productivity/mindmap-3.png&#34; alt=&#34;Prioritized Mind Map&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;Here‘s where Things 3 comes into the picture. I follow some simple rules that helps this all stay together. I create &amp;ldquo;Areas&amp;rdquo; for each portion of my life. Most importantly I‘ll have one called &amp;ldquo;Self&amp;rdquo; and several for the other areas. Note these aren&amp;rsquo;t &amp;ldquo;Projects&amp;rdquo;. I define an Area as something that can contain Projects or individual tasks. As an example, the &amp;ldquo;Work&amp;rdquo; area may have a project like &amp;ldquo;Release v1.2&amp;rdquo; or individual task like &amp;ldquo;Set up meeting with Joe&amp;rdquo;. I don&amp;rsquo;t ever create projects like &amp;ldquo;Misc&amp;rdquo;, a project is defined as something that can be completed, all other tasks just live in the area itself.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/productivity/projects.png&#34; alt=&#34;Projects in Things&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;Now how do I apply the prioritization? Tags. Things 3 has a nice tag system and you can add a tag with some hot keys like &lt;code&gt;ctrl + 1&lt;/code&gt;. This is especially great since I prioritize these by 1-4. This means new tasks can be immediately prioritized when I create a task. This is one of the key things to me. Even though I love the above method for brain dumping, I get new tasks or ideas all throughout the day. This tag system means I can give an immediate prioritization and even a target day to work on it.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/productivity/newtask.png&#34; alt=&#34;New Task&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;Finally this all comes back to the day to day. Every morning I look through each of my projects and determine if I can work on it today or not. Now I have a list of todos that need to happen today. This crosses across all areas of my life. It‘s the source of truth of what I need to do.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/productivity/today.png&#34; alt=&#34;Today&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;Things 3 also allows you to filter a view by tag. Since tags represent priority, I can choose to only see those urgent high value tasks when I have high energy, and choose less urgent tasks when I’m lower on energy.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/assets/images/productivity/filtered.png&#34; alt=&#34;Today, filtered&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;This all seems like a lot. It‘s not really. The brain dump only happens when there are a significant number of tasks. Otherwise, I just add things as they come up. This creates a system that is easy to manage and not overwhelming. This helps me keep up with tasks more. Also, individual projects get a nice little progress indicator which helps me see at a glance how they are doing. For me a system has to be extremely simple to reason about and easy to manage. Otherwise, I‘m incredibly capable of avoiding the work of managing my time and allow myself to focus on things that aren‘t as valuable. I‘ve been doing this now for a few weeks and it‘s going well. I can‘t say it‘s the final system I‘ll use but I think it‘s the most likely going to stick around for some time.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Cross Platform Thoughts</title>
      <link>https://jnjosh.com/workshop/posts/cross-platform-thoughts/</link>
      <pubDate>Sun, 18 Aug 2019 12:27:06 -0500</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/cross-platform-thoughts/</guid>
      <description>&lt;p&gt;Cross Platform development for most mobile apps is just simply not worth it. Sure, there are great open source options like React Native and just plain C++ but you end up spending the same time trying to be clever and &amp;ldquo;share code&amp;rdquo; when you could have just built your product with all the platform has to offer. There seems to be a lot of realizations this year with &lt;a href=&#34;https://blogs.dropbox.com/tech/2019/08/the-not-so-hidden-cost-of-sharing-code-between-ios-and-android/&#34;&gt;Dropbox&lt;/a&gt; dropping their C++ components and last year &lt;a href=&#34;https://medium.com/airbnb-engineering/sunsetting-react-native-1868ba28e30a&#34;&gt;Airbnb&lt;/a&gt; sunsetting React Native components. I&amp;rsquo;ve played this game before and while it was a great challenge, I would advise anyone not to do it for most cases.&lt;/p&gt;&#xA;&lt;p&gt;Back in 2017 I joined the &lt;a href=&#34;http://joinhighrise.com&#34;&gt;Highrise team&lt;/a&gt; as the Lead Mobile Engineer and one of the first decisions we needed to make was: what is the future of our game and how could we create it so that it could live on Android?&lt;/p&gt;&#xA;&lt;p&gt;Highrise is unique in that it&amp;rsquo;s both an app and a game. We quickly explored some cross platform options for making the game components like Unreal, Unity, libGDX, etc. These options seemed great for the parts of the app that were a game but not so great for the standard app components. We&amp;rsquo;d end up having &amp;ldquo;crappy Game UI&amp;rdquo;™ which was explicitly something we did &lt;em&gt;not&lt;/em&gt; want. We wanted the app to feel as good as Instagram or any Apple app when not in game-based UIs. I proposed the best path as either &amp;ldquo;let&amp;rsquo;s not be clever and just make the app twice&amp;rdquo; or &amp;ldquo;let&amp;rsquo;s build the game components in a shared C++ layer and the app components natively&amp;rdquo;. I had done this previously when working on BA3&amp;rsquo;s mapping engine and felt this could be a great solution for our problem. We could write the hard game-based code once and write specific UIs for each platform. I shared this at local meetups as &lt;a href=&#34;https://speakerdeck.com/jnjosh/cross-platform-shenanigans-part-1-ios&#34;&gt;cross platform&lt;/a&gt; &lt;a href=&#34;https://speakerdeck.com/jnjosh/cross-platform-shenanigans-part-2-android&#34;&gt;shenanigans&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;You know what? It was actually &lt;em&gt;pretty fun&lt;/em&gt;! We ended up with an extra platform, C++, and a lot of bridging code in Objective-C++ (and JNI-based C for Android) and had to make a lot of calls on how we&amp;rsquo;d communicate game events between the UI and core layers. We worked with &lt;a href=&#34;https://google.github.io/flatbuffers/&#34;&gt;Flatbuffers&lt;/a&gt; which gave us simple data to pass around. We now had a platform that we could build on top of, we had a core layer that was compatible with multiple platforms, and we had a UI layer that would not be foreign to most iOS or Android developers. I&amp;rsquo;d even go as far as saying if you are building a game, this is probably the best way to get cross platform game logic while not sacrificing a great app experience.&lt;/p&gt;&#xA;&lt;p&gt;Would I do it again? No.&lt;/p&gt;&#xA;&lt;p&gt;The thing is we really didn&amp;rsquo;t save much time. Most features ended up being full stack features so we couldn&amp;rsquo;t isolate a developer from the Core as much as we wanted. This meant that we had to expose bridge objects going both ways—which is not a simple problem—for most features. We also had to deal with many layers of abstraction making things like simple networking much harder to debug. In the end, we spent about the same amount of time as if we had just built the app for each platform. We could have even saved some time when you consider we could us one platform as a guide to build the other platform and not fight issues that worked well in one platform and not on the other.&lt;/p&gt;&#xA;&lt;p&gt;One of the first things I teach new developers is &amp;ldquo;don&amp;rsquo;t fight the platform&amp;rdquo;. Instead of trying to be clever and solving your too many platforms problem by adding another platform, just build your app simply. Build in Swift with all that Apple offers or build in Kotlin with all that Google offers. You&amp;rsquo;ll appreciate the simplicity of it when you have to debug any issue. You may have to think a little bit harder to coordinate releases between the platforms, but that is a far simpler problem than trying to build something that works great on whatever platform it runs on. I&amp;rsquo;ve since moved on from Highrise and BA3, but one lesson I&amp;rsquo;ll take with me is don&amp;rsquo;t try to be too clever.&lt;/p&gt;</description>
    </item>
    <item>
      <title>On Hiring at Highrise</title>
      <link>https://gusto.com/resources/articles/hr/hiring/hiring-document-highrise</link>
      <pubDate>Sat, 31 Mar 2018 19:24:55 -0400</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/onhiringathighrise/</guid>
      <description>&lt;p&gt;Late last year at &lt;a href=&#34;http://www.joinhighrise.com&#34;&gt;Highrise&lt;/a&gt; we were working on hiring a new server engineer for our team. We weren&amp;rsquo;t really clear what we were looking for and different parts of the team had different ideas about the ideal candidate. I worked with the team to overhaul our system to create a &lt;a href=&#34;http://www.joinhighrise.com/blog/engineering/hiring-engineers-is-hard/&#34;&gt;better process for choosing candidates&lt;/a&gt;. Last week Gusto published an &lt;a href=&#34;https://gusto.com/resources/articles/hr/hiring/hiring-document-highrise&#34;&gt;article about our system which is a really great write-up about our system&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://jnjosh.com/workshop/posts/onhiringathighrise/&#34;&gt;Permalink&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Debugging with Sound</title>
      <link>https://jnjosh.com/workshop/posts/debugwithsound/</link>
      <pubDate>Thu, 08 Feb 2018 22:27:06 -0500</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/debugwithsound/</guid>
      <description>&lt;p&gt;Debugging is hard. You have to remember what you or someone else was trying to do while understanding what&amp;rsquo;s actually going on. I don&amp;rsquo;t remember where I heard it, but there is a saying that goes something like &amp;ldquo;Don&amp;rsquo;t write code at the peak of your understanding because debugging it requires 2 times the understanding…&amp;rdquo;. Sometimes it&amp;rsquo;s not that it&amp;rsquo;s hard it&amp;rsquo;s that there is just too much going on. For these cases, I&amp;rsquo;ve been using sound.&lt;/p&gt;&#xA;&lt;p&gt;One case where this comes in handy is debugging a network call and cache hit. Imagine you are building a system that checks an in-memory cache, an on-disk cache, and a download operation. You&amp;rsquo;ve probably done this several times. Debugging this is hard because you really don&amp;rsquo;t want to stop, you want to know that it&amp;rsquo;s hitting all the different points appropriately. Sure logging is fine here, but that&amp;rsquo;s prone to confusion if you have a noisy log. What I find helpful when debugging things like this—or any type of retain/release pattern—is to add a sound-based break point.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;    &lt;img src=&#34;https://jnjosh.com/uploads/2018/02/08/debug.png&#34; alt=&#34;&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34;&gt;&#xA;  &lt;/figure&gt;&lt;p&gt;Now I don&amp;rsquo;t have to study and read, I just use the app and listen. If I hear a &lt;code&gt;Ping&lt;/code&gt;, a &lt;code&gt;Funk&lt;/code&gt;, and a &lt;code&gt;Frog&lt;/code&gt; sound when I&amp;rsquo;m expecting it—I know I&amp;rsquo;m on the right track. The great thing is you also don&amp;rsquo;t have to stop the debug session, you can simply add these while debugging.&lt;/p&gt;&#xA;&lt;p&gt;I think this is a great tool and it&amp;rsquo;s kind of fun! I don&amp;rsquo;t use it all the time but every once in a while I find something that fits this debugging tool well. Then I can listen to my code instead of reading it.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Calling Kotlin from C&#43;&#43;</title>
      <link>https://jnjosh.com/workshop/posts/kotlinfromcpp/</link>
      <pubDate>Mon, 05 Feb 2018 23:37:13 -0500</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/kotlinfromcpp/</guid>
      <description>&lt;p&gt;At &lt;a href=&#34;http://www.joinhighrise.com&#34;&gt;Highrise&lt;/a&gt; we&amp;rsquo;ve been building the future of our platform in a way that supports Android. We&amp;rsquo;ve put together a stack that is pretty challenging but really fun to build. It&amp;rsquo;s based on a C++ core and platform specific projects in Swift (for iOS) and Kotlin (for Android) that uses that core. On Swift you need to have an Objective-C++ wrapper for Swift to call functionality in the C++ core, but it&amp;rsquo;s pretty straightforward and things like wrapping a block in an &lt;code&gt;std::function&lt;/code&gt; works pretty well. Android however runs on the JVM and requires using JNI to communicate with C++. This took away the niceness of Kotlin lambdas for asynchronous completion away from us. Or did it?&lt;/p&gt;&#xA;&lt;p&gt;I first tried to pass a kotlin lambda to the core and wasn&amp;rsquo;t able to make it work. My fallback was to create a convention of well-defined callbacks. For example, a call to &amp;lsquo;&lt;code&gt;loginUser&lt;/code&gt;&amp;rsquo; would result, eventually, in a call to &amp;lsquo;&lt;code&gt;callbackLoginUser&lt;/code&gt;&amp;rsquo;. This required a lot of overhead because of how JNI works. For each &lt;code&gt;callback&lt;/code&gt;-prefixed function I had to build an interface that hard-coded the java parameter code, the method, and the class.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;JniMethodInfo&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;callbackLoginUserMethod&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;n&#34;&gt;JniMethodInfo&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;callbackInfo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;n&#34;&gt;JniHelper&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;getMethodInfo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;callbackInfo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                              &lt;span class=&#34;s&#34;&gt;&amp;#34;com/app/android/Login&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                              &lt;span class=&#34;s&#34;&gt;&amp;#34;callbackLoginUser&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                              &lt;span class=&#34;s&#34;&gt;&amp;#34;(ZLjava/lang/String;)V&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;callbackInfo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;callbackLoginUser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;jobject&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &#x9;&#x9;&#x9;&#x9;&#x9;    &#x9;   &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;succeeded&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &#x9;&#x9;&#x9;&#x9;&#x9;    &#x9;   &lt;span class=&#34;n&#34;&gt;std&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;errorMessage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;k&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;info&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;callbackLoginUserMethod&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;k&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;errorString&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;NewStringUTF&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;errorMessage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;c_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;());&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;n&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CallVoidMethod&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;methodID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;succeeded&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;errorString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;n&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DeleteGlobalRef&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;These are basically helper methods to hide the boilerplate JNI code behind a simple function call. It works and is easy to reason about since we created a common convention. That said, I couldn&amp;rsquo;t stop thinking about being able to use kotlin lambdas. Of course, the idea came to me on the drive home.&lt;/p&gt;&#xA;&lt;p&gt;First thing I thought was since everything in Java (and Kotlin) are objects, a lambda must also be a class that gets generated in the JVM. After a little study, it does!&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-kotlin&#34; data-lang=&#34;kotlin&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;k&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Boolean&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Unit&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &#x9;&lt;span class=&#34;n&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;$value&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; -&amp;gt; &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;$success&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;For example that code becomes a class is created with a name like: &lt;code&gt;com.app.android.Login$loginUser$completion$1&lt;/code&gt;. And an &lt;code&gt;invoke&lt;/code&gt; function with the right params is created: &lt;code&gt;invoke(boolean, java.lang.String)&lt;/code&gt;. With this knowledge I just had to figure out how to actually call the invoke function, which was pretty clear with all the boilerplate JNI I had written.&lt;/p&gt;&#xA;&lt;p&gt;First I needed to acquire the name of the class. JNI functions are basically C functions that you need to manage, so the lambda shows up on the C++ side as a &lt;code&gt;jobject&lt;/code&gt;. The matching JNI function will receive a jobject instance of the lambda.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-kotlin&#34; data-lang=&#34;kotlin&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;c1&#34;&gt;// In Login.kt, identify the kotlin external function that will call to C++&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;k&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;external&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;nativeLoginUser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Boolean&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Unit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;c1&#34;&gt;// In Login.cpp, define the C function that will be matched with the above&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;c1&#34;&gt;// external function.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;n&#34;&gt;JNIEXPORT&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;Java_com_app_android_Login_nativeLoginUser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;JNIEnv&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;jobject&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;thiz&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;jobject&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &#x9;&lt;span class=&#34;c1&#34;&gt;//…&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;The first task will be to identify the generated class of this completion object.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;n&#34;&gt;JNIEXPORT&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;Java_com_app_android_Login_nativeLoginUser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;JNIEnv&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;jobject&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;thiz&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;jobject&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;n&#34;&gt;jclass&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;klass&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetObjectClass&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;jmethodID&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;classMethodId&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetMethodID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;klass&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;   &lt;span class=&#34;s&#34;&gt;&amp;#34;getClass&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;   &lt;span class=&#34;s&#34;&gt;&amp;#34;()Ljava/lang/Class;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;jobject&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;klassObj&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CallObjectMethod&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;classMethodId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;klassObject&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetObjectClass&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;klassObj&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;nameMethodId&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetMethodID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;klassObject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#x9;                                         &lt;span class=&#34;s&#34;&gt;&amp;#34;getName&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#x9;                                         &lt;span class=&#34;s&#34;&gt;&amp;#34;()Ljava/lang/String;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;jstring&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;classString&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;jstring&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CallObjectMethod&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;klassObj&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;     &lt;span class=&#34;n&#34;&gt;nameMethodId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;className&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GetStringUTFChars&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;classString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;//…&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Now that we know what class we are communicating with, we&amp;rsquo;ll need to find the invoke method. This part is still a bit hard-coded, unfortunately. You have to provide a param code that matches tha actual method signature. Something like &amp;ldquo;&lt;code&gt;(ZLjava/lang/String;)V&lt;/code&gt;&amp;rdquo;. This translates Z to &lt;code&gt;Boolean&lt;/code&gt;, L to &lt;code&gt;java/lang/String&lt;/code&gt;, and V to &lt;code&gt;Void&lt;/code&gt;. This matches the signature of the lambda (except for that Unit type, which I don&amp;rsquo;t fully understand). Using the &lt;code&gt;JniHelper&lt;/code&gt; class that comes with &lt;a href=&#34;http://www.cocos2d-x.org&#34;&gt;Cocos2d-X&lt;/a&gt; I can get that method information. Finally I can call the method on the specified object with the vargs of the arguments to send.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;n&#34;&gt;JNIEXPORT&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;Java_com_app_android_Login_nativeLoginUser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;JNIEnv&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;jobject&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;thiz&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;jobject&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;c1&#34;&gt;// Get `className` (above)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;JniMethodInfo&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;invokeMethod&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;JniHelper&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;getMethodInfo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;invokeMethod&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                             &lt;span class=&#34;n&#34;&gt;className&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                             &lt;span class=&#34;s&#34;&gt;&amp;#34;invoke&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                             &lt;span class=&#34;s&#34;&gt;&amp;#34;(ZLjava/lang/String;)V&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;jstring&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;message&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;JniHelper&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;getEnv&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;NewStringUTF&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Success!&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;invokeMethod&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CallVoidMethod&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;invokeMethod&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;methodID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9; &lt;span class=&#34;n&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ReleaseStringUTFChars&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;classNameString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;className&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Finally, in Kotlin I can simply add a new function that calls the native/external C++ function &lt;em&gt;with the lambda&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-kotlin&#34; data-lang=&#34;kotlin&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;k&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;external&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;nativeLoginUser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Boolean&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Unit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;loginUser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;nativeLoginUser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;value&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Yes!&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;There is a lot I didn&amp;rsquo;t cover here like releasing objects properly and getting a global reference to objects that you may need to wait for asynchronous callback. That&amp;rsquo;s all JNI specific stuff and it, unfortunately, takes a lot of trial and error to work out. I&amp;rsquo;m also not totally happy with the way the param code is hard coded. I think I could probably generate the param string from the Kotlin side and pass that with the block. It&amp;rsquo;s not ideal but still better than the convention-based method. Have you done this? I&amp;rsquo;d love to hear your experiences!&lt;/p&gt;</description>
    </item>
    <item>
      <title>On using Things 3</title>
      <link>http://blog.bellebcooper.com/how-i-use-things-3.html</link>
      <pubDate>Mon, 05 Feb 2018 11:45:14 -0500</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/usingthings/</guid>
      <description>&lt;p&gt;I really enjoyed this post about using Things 3. I&amp;rsquo;ve &lt;a href=&#34;https://jnjosh.com/microposts/2017-05-20-175743/&#34;&gt;been using it&lt;/a&gt; off and on since last year but this post has already changed how I structure my projects. The funny thing is I now have more areas and projects but I don&amp;rsquo;t feel as overloaded as when I had a more dense hierarchy. I previously had projects children of companies or bigger contexts. Now projects have their own area.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://jnjosh.com/workshop/posts/usingthings/&#34;&gt;Permalink&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Static Blog Automation</title>
      <link>https://jnjosh.com/workshop/posts/static-blog-automation/</link>
      <pubDate>Fri, 19 Jan 2018 00:11:12 -0500</pubDate><guid isPermaLink="false">https://jnjosh.com/workshop/posts/static-blog-automation/</guid>
      <description>&lt;p&gt;As I&amp;rsquo;ve written about in the past &lt;a href=&#34;https://jnjosh.com/posts/goodbye-octopress-hello-hugo/&#34;&gt;this blog is powered by Hugo&lt;/a&gt;—a static site generator. That&amp;rsquo;s great and all but it really limits when I can write posts. Something running Hugo needs to build the site and that output needs to be published somewhere. I&amp;rsquo;ve done a few things to make this process a more automatic.&lt;/p&gt;&#xA;&lt;!-- more --&gt;&#xA;&lt;h2 id=&#34;writing-posts-on-my-macbook-pro&#34;&gt;Writing Posts on my MacBook Pro&lt;/h2&gt;&#xA;&lt;p&gt;First of all I needed to make it much easier to create a new post. &lt;a href=&#34;https://github.com/jnjosh/internet-weblog&#34;&gt;My theme&lt;/a&gt; has three types of post content and it&amp;rsquo;s not easy to remember which command I need to do the right type of post. To solve this I created some simple bash scripts to help out.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; newmicropost&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;date +%Y-%m-%d-%H%M%S&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;microposts/&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.md&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    hugo new &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; cut -d &lt;span class=&#34;s1&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; -f &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; xargs subl &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;// &lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt; The name of the file to create&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; newpost&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    hugo new &lt;span class=&#34;s2&#34;&gt;&amp;#34;posts/&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.md&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; cut -d &lt;span class=&#34;s1&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; -f &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; xargs subl&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;// &lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt; The name of the file to create&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; newphotopost&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;hugo new &lt;span class=&#34;s2&#34;&gt;&amp;#34;photos/&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.md&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; cut -d &lt;span class=&#34;s1&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; -f &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; xargs subl&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This is really great when I want to publish a &lt;a href=&#34;https://micro.blog&#34;&gt;Micro.blog&lt;/a&gt; post about some idea that pops up while working. I&amp;rsquo;m already in the terminal normally so jumping to my blog directory and typing &lt;code&gt;newmicropost&lt;/code&gt; is pretty quick.&lt;/p&gt;&#xA;&lt;h2 id=&#34;writing-posts-on-my-ipad-or-iphone&#34;&gt;Writing Posts on my iPad or iPhone&lt;/h2&gt;&#xA;&lt;p&gt;This is a bit more difficult and I&amp;rsquo;m still working out how to best do this. The biggest key was to not have to run Hugo myself. Offloading that task (and hosting) to &lt;a href=&#34;https://www.netlify.com&#34;&gt;Netlify&lt;/a&gt; powered by a Github webhook means I can create and publish from any device simply by commiting to Github.&lt;/p&gt;&#xA;&lt;p&gt;An app like &lt;a href=&#34;https://workingcopyapp.com&#34;&gt;Working Copy&lt;/a&gt; or &lt;a href=&#34;http://clone.hammockdistrict.com&#34;&gt;Clone&lt;/a&gt; makes this possible. It&amp;rsquo;s admittedly a bit of a chore to post from the iPhone because you have to not only create the markdown file in the right place, but also create the correct front matter so that Hugo can properly generate your page. My current process is to write a post in &lt;a href=&#34;https://ulyssesapp.com&#34;&gt;Ulysses&lt;/a&gt;, where I have a document with all the expected front matter. Then when I am ready to publish I copy that over to Working Copy and push a commit.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;d like this all to be better. I&amp;rsquo;m really interested in what others have done here. I love having a static website, but the nature of it puts a pretty nasty obstacle in my way.&lt;/p&gt;&#xA;</description>
    </item>
  </channel>
</rss>
