<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>David's Obsessions</title><link>https://www.davidsobsessions.com/</link><description>Recent content on David's Obsessions</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 07 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.davidsobsessions.com/index.xml" rel="self" type="application/rss+xml"/><item><title>MTG Whiteboard Tokens</title><link>https://www.davidsobsessions.com/p/mtg-tokens/</link><pubDate>Tue, 07 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.davidsobsessions.com/p/mtg-tokens/</guid><description>&lt;img src="https://www.davidsobsessions.com/p/mtg-tokens/tokens.jpg" alt="Featured image of post MTG Whiteboard Tokens" /&gt;&lt;p&gt;One of my on again off again obsessions is Magic the Gathering. My friends and I play a lot of commander which always requires lots of fiddly counters and a random assortment of tokens.&lt;/p&gt;
&lt;p&gt;I got sick of using miscellaneous bits of paper to represent tokens so I designed these dry erase tokens.&lt;/p&gt;
&lt;p&gt;The front of the token can be used to represent tokens, with a space for indicating the color, type, power and toughness.&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="320px" data-flex-grow="133" height="3024" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/mtg-tokens/dry-erase.jpg" srcset="https://www.davidsobsessions.com/p/mtg-tokens/dry-erase_hu_6ea316e2bdfd7724.jpg 800w, https://www.davidsobsessions.com/p/mtg-tokens/dry-erase_hu_82beef52f419e3cd.jpg 1600w, https://www.davidsobsessions.com/p/mtg-tokens/dry-erase_hu_87be9f8645763e39.jpg 2400w, https://www.davidsobsessions.com/p/mtg-tokens/dry-erase.jpg 4032w" width="4032"&gt;&lt;/p&gt;
&lt;p&gt;The back can be used to keep track of the overall strength of a roided out commander, or the number of +1/+1 counters on a creature.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Track Counters" class="gallery-image" data-flex-basis="320px" data-flex-grow="133" height="3024" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/mtg-tokens/counters.jpg" srcset="https://www.davidsobsessions.com/p/mtg-tokens/counters_hu_2a41aa98fc4c5119.jpg 800w, https://www.davidsobsessions.com/p/mtg-tokens/counters_hu_ad87525e7f551a7b.jpg 1600w, https://www.davidsobsessions.com/p/mtg-tokens/counters_hu_f9261ca5b5aa0fc6.jpg 2400w, https://www.davidsobsessions.com/p/mtg-tokens/counters.jpg 4032w" width="4032"&gt; &lt;img alt="Track the overall status of a creature" class="gallery-image" data-flex-basis="320px" data-flex-grow="133" height="3024" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/mtg-tokens/equipment.jpg" srcset="https://www.davidsobsessions.com/p/mtg-tokens/equipment_hu_e56952113f9e9505.jpg 800w, https://www.davidsobsessions.com/p/mtg-tokens/equipment_hu_8d15c75a008a117d.jpg 1600w, https://www.davidsobsessions.com/p/mtg-tokens/equipment_hu_8e3f0529d478ca71.jpg 2400w, https://www.davidsobsessions.com/p/mtg-tokens/equipment.jpg 4032w" width="4032"&gt;&lt;/p&gt;
&lt;h2 id="make-your-own"&gt;Make Your Own
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="mtg_token_dry_erase_front.pdf" &gt;Token side PDF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="mtg_token_dry_erase_back.pdf" &gt;Counter side PDF&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I like to make my tokens double sided. If you want to do it this way, make sure that the paper aligns correctly in your printer, otherwise you can end up with parts of the card that get cut. If your printer can do double sided print jobs then this may not be an issue.&lt;/li&gt;
&lt;li&gt;MAKE SURE YOU PRINT AT 100% size! Otherwise the tokens will be smaller than a standard magic card&lt;/li&gt;
&lt;li&gt;Use 8.5in by 11in paper. A4 will probably work as well, just make sure it&amp;rsquo;s scaled correctly.&lt;/li&gt;
&lt;li&gt;I like to use card stock instead of regular printer paper. It makes the cards feel nice and hefty&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you&amp;rsquo;ve printed out the tokens you can either laminate them and then cut them or cut them and put packing tape on the front.&lt;/p&gt;
&lt;h2 id="remix-the-design"&gt;Remix the Design
&lt;/h2&gt;&lt;p&gt;The original SVGs for the cards &lt;a class="link" href="tokens.svg" &gt;are available here&lt;/a&gt;. You can use any vector editor such as &lt;a class="link" href="https://www.inkscape.org/" target="_blank" rel="noopener"
 &gt;Inkscape&lt;/a&gt; or Adobe Illustrator. The designs are licensed under the &lt;a class="link" href="https://mit-license.org/" target="_blank" rel="noopener"
 &gt;MIT open source license&lt;/a&gt;, so feel free to use, modify and re-distribute them!&lt;/p&gt;</description></item><item><title>Digital Carrot</title><link>https://www.davidsobsessions.com/p/digital-carrot/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.davidsobsessions.com/p/digital-carrot/</guid><description>&lt;img src="https://www.davidsobsessions.com/p/digital-carrot/logo-full.png" alt="Featured image of post Digital Carrot" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;I wanted to get in shape before my honeymoon back in 2025 so that I would look good in pictures and be able to handle all of the walking my wife and I were planning. I created a Python script that read how many steps I took each day from my Garmin watch and then blocked youtube until I hit 10,000 steps. This was wildly successful, helping me lose 20lb as well as get some of my focus back! I decided to that this was a product worth sharing with the world, so I created Digital Carrot.&lt;/p&gt;
&lt;p&gt;Digital Carrot is a focus app, app blocker and productivity tool. It motivates you to achieve your goals by blocking apps, websites and video games until you achieve whatever goals you set for yourself. Goals are all set and checked pragmatically. If you set a goal to walk a certain number of steps, the app will check how many steps you actually walked in Apple or Google health.&lt;/p&gt;
&lt;p&gt;For example you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Block Instagram until you walk 10,000 steps&lt;/li&gt;
&lt;li&gt;Block League of Legends until the heat death of the universe because LoL is consuming your soul and you&amp;rsquo;re better off without it&lt;/li&gt;
&lt;li&gt;Allow yourself 30 minutes a day to browse Reddit&lt;/li&gt;
&lt;li&gt;Block TikTok until you spend 60 minutes at the gym&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Goal overview" class="gallery-image" data-flex-basis="150px" data-flex-grow="62" height="1880" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/digital-carrot/home.png" srcset="https://www.davidsobsessions.com/p/digital-carrot/home_hu_967b53cbe9e87196.png 800w, https://www.davidsobsessions.com/p/digital-carrot/home.png 1179w" width="1179"&gt; &lt;img alt="Block social media until you walk 6000 steps" class="gallery-image" data-flex-basis="220px" data-flex-grow="92" height="1281" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/digital-carrot/step_goal.png" srcset="https://www.davidsobsessions.com/p/digital-carrot/step_goal_hu_b5b863b9e646cf59.png 800w, https://www.davidsobsessions.com/p/digital-carrot/step_goal.png 1179w" width="1179"&gt; &lt;img alt="Block games until you finish your todo list" class="gallery-image" data-flex-basis="187px" data-flex-grow="78" height="1506" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/digital-carrot/todo_list.png" srcset="https://www.davidsobsessions.com/p/digital-carrot/todo_list_hu_94cb9283756523bc.png 800w, https://www.davidsobsessions.com/p/digital-carrot/todo_list.png 1179w" width="1179"&gt;&lt;/p&gt;
&lt;p&gt;Everyone has their own set of goals that they want to achieve, so the app is designed from the ground up to be as customizable as possible. It features a plugin system that allows the app to be extended to fetch data for creating goals from anywhere. Goals are then defined pragmatically using a very simple scripting system. You can create goals using quick start templates or define your own.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Quickstart Goal to Climb Stairs" class="gallery-image" data-flex-basis="336px" data-flex-grow="140" height="535" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/digital-carrot/qs_stairs.png" width="750"&gt; &lt;img alt="Custom goal to walk 500 steps every hour" class="gallery-image" data-flex-basis="272px" data-flex-grow="113" height="1038" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/digital-carrot/custom_goal.png" srcset="https://www.davidsobsessions.com/p/digital-carrot/custom_goal_hu_ae5949f379edebd8.png 800w, https://www.davidsobsessions.com/p/digital-carrot/custom_goal.png 1179w" width="1179"&gt;&lt;/p&gt;
&lt;h2 id="features"&gt;Features
&lt;/h2&gt;&lt;h3 id="pc-mobile-and-sync"&gt;PC, Mobile and Sync
&lt;/h3&gt;&lt;p&gt;Digital Carrot is more than just a mobile app!&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s available on Windows and Mac as well as Android and iOS.&lt;/p&gt;
&lt;p&gt;Having a PC version is personally very important to me because I&amp;rsquo;m what the kids refer to as an &lt;a class="link" href="https://www.urbandictionary.com/define.php?term=Unc." target="_blank" rel="noopener"
 &gt;&amp;ldquo;unc&amp;rdquo;&lt;/a&gt; who prefers to watch YouTube on his laptop.&lt;/p&gt;
&lt;p&gt;The app has an optional paid feature to sync your goals and blocked items across all your devices. Sync is fully end to end encrypted. I ended up developing my own sync system rather than relying on iCloud or Dropbox for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sync has to be FAST. Some data (such as Apple Watch information) is only available on your phone. When you complete your step goal, you DESERVE to be rewarded right away on your computer. You can&amp;rsquo;t wait around for the iCloud gremlins to wake up when there&amp;rsquo;s brain rot to be had!&lt;/li&gt;
&lt;li&gt;Sync has to support all devices. I have a gaming PC, MacBook and iPhone. Services like iCloud won&amp;rsquo;t sync to Windows.&lt;/li&gt;
&lt;li&gt;Sync MUST be PRIVATE. iCloud has good encryption policies, but the only way to guarantee security is to do it myself.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="480px" data-flex-grow="200" height="400" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/digital-carrot/thanos.jpg" width="800"&gt;&lt;/p&gt;
&lt;p&gt;Plus building a high performance, end to end encrypted sync was a lot of fun!&lt;/p&gt;
&lt;h3 id="commitment-mode---keep-yourself-from-cheating"&gt;Commitment Mode - Keep yourself from cheating
&lt;/h3&gt;&lt;p&gt;Commitment mode allows you to lock down your configuration with a challenge. Right now this includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Entering a long string of random text&lt;/li&gt;
&lt;li&gt;Putting in a password&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As with everything else in the app, unlock challenges are configurable via plugins, so these can be customized to whatever you want!&lt;/p&gt;
&lt;p&gt;The app also features uninstall prevention to keep you from cheating by just uninstalling the app.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Uninstall prevention" class="gallery-image" data-flex-basis="319px" data-flex-grow="133" height="563" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/digital-carrot/uninstall.png" width="750"&gt; &lt;img alt="Unlock challenge" class="gallery-image" data-flex-basis="338px" data-flex-grow="141" height="836" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/digital-carrot/random_text.png" srcset="https://www.davidsobsessions.com/p/digital-carrot/random_text_hu_d283db33f8eb77e2.png 800w, https://www.davidsobsessions.com/p/digital-carrot/random_text.png 1179w" width="1179"&gt;&lt;/p&gt;
&lt;h3 id="passes---take-a-break-when-you-need-one"&gt;Passes - Take a break when you need one
&lt;/h3&gt;&lt;p&gt;One issue I ran into from the beginning was that there are just some days that I can&amp;rsquo;t accomplish my goals. This could be because I got sick, was traveling or just wanted some extra time to play Abiotic Factor with my buddies. Getting sick was especially problematic, because I would end up disabling my website blocker and then never re-enable it.&lt;/p&gt;
&lt;p&gt;The fix here is passes. Passes can be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Awarded on a monthly basis&lt;/li&gt;
&lt;li&gt;Earned by completing a goal on a rest day&lt;/li&gt;
&lt;li&gt;Earned by completing 2x your goal (such as by walking 20,000 steps on a 10,000 step goal)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can spend a pass to skip your goal for a day if you need to.&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="376px" data-flex-grow="156" height="752" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/digital-carrot/passes.png" srcset="https://www.davidsobsessions.com/p/digital-carrot/passes_hu_353b931166bbde8.png 800w, https://www.davidsobsessions.com/p/digital-carrot/passes.png 1179w" width="1179"&gt;&lt;/p&gt;
&lt;p&gt;Passes will be able to be configured pragmatically in an upcoming update! This means you&amp;rsquo;ll be able to do things such give yourself a pass if it&amp;rsquo;s raining or if the price of Bitcoin goes to the moon.&lt;/p&gt;
&lt;h3 id="rules---configure-how-and-when-your-distractions-are-blocked"&gt;Rules - Configure how and when your distractions are blocked
&lt;/h3&gt;&lt;p&gt;Rules let you define conditions when your blocked items are either blocked or unblocked. You can use a rule to always block everything during work hours or to unblock a block list for 30 minutes each day.&lt;/p&gt;
&lt;p&gt;Rules are also customize-able. If you wanted to, you could create a rule that unblocks Twitch when your favorite streamer is live!&lt;/p&gt;
&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="419px" data-flex-grow="174" height="675" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://www.davidsobsessions.com/p/digital-carrot/rules.png" srcset="https://www.davidsobsessions.com/p/digital-carrot/rules_hu_6976a0bbb9d08837.png 800w, https://www.davidsobsessions.com/p/digital-carrot/rules.png 1179w" width="1179"&gt;&lt;/p&gt;
&lt;h3 id="plugins---create-goals-from-anything"&gt;Plugins - Create goals from anything
&lt;/h3&gt;&lt;p&gt;Plugins allow you to bring any information you want into the app. If you&amp;rsquo;d like to contribute a plugin there&amp;rsquo;s &lt;a class="link" href="https://www.digitalcarrot.app/docs/developers/" target="_blank" rel="noopener"
 &gt;documentation for it here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="download-the-app"&gt;Download the App
&lt;/h2&gt;&lt;p&gt;Get the app at &lt;a class="link" href="https://www.digitalcarrot.app/" target="_blank" rel="noopener"
 &gt;www.digitalcarrot.app&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="pricing"&gt;Pricing
&lt;/h2&gt;&lt;p&gt;The app is 100% free on PC (Windows and Mac).&lt;/p&gt;
&lt;p&gt;The mobile version is freemium with some features locked behind a paywall. You can unlock all the features on mobile for a $30 one time payment.&lt;/p&gt;
&lt;p&gt;Sync is available with a $60/yr or $8/mo subscription.&lt;/p&gt;
&lt;p&gt;The app is priced this way for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I have to pay a developer fee to distribute to Android and iOS, which is offset by the freemium model&lt;/li&gt;
&lt;li&gt;The desktop version is required for people to develop plugins and I want anyone to be able to develop plugins without having to buy the app&lt;/li&gt;
&lt;li&gt;Sync is fairly expensive to maintain and requires me to constantly monitor the sync server&lt;/li&gt;
&lt;li&gt;I refuse to put ads in the app or to sell user data. Charging a small fee for the app allows me to keep maintaining it while also respecting my user&amp;rsquo;s time and privacy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I really appreciate everyone who chooses to pay for the app! It&amp;rsquo;s built by one person, and my dream is to be able to make a living supporting the app.&lt;/p&gt;</description></item><item><title>Using Go for Mobile Apps</title><link>https://www.davidsobsessions.com/p/one-year-of-gomobile/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.davidsobsessions.com/p/one-year-of-gomobile/</guid><description>&lt;img src="https://www.davidsobsessions.com/p/one-year-of-gomobile/head.png" alt="Featured image of post Using Go for Mobile Apps" /&gt;&lt;p&gt;I have been developing Digital Carrot with Go Mobile for the last year and it has been a largely positive experience. This article is meant to serve as a repository of everything I&amp;rsquo;ve learned throughout the process.&lt;/p&gt;
&lt;h2 id="why-did-i-choose-go-mobile"&gt;Why did I choose Go Mobile?
&lt;/h2&gt;&lt;p&gt;The short answer is that I really like Go and wanted to use it. The longer answer is that is some combination of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Digital Carrot is a cross platform application, and I wanted something that I could easily compile and run anywhere.&lt;/li&gt;
&lt;li&gt;Go has a strong library of system features that I wanted to use to make Digital Carrot customizable and pluggable. Chief among these are Expr (for building expressions) and Goja (for creating JavaScript plugins).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-am-i-using-go-mobile"&gt;How am I using Go Mobile?
&lt;/h2&gt;&lt;p&gt;Go mobile is used for all the business logic for Digital Carrot. While I would have loved to use Fyne for the UI, I gave it a try and decided it just isn&amp;rsquo;t quite mature enough. Instead, I opted to use Flutter with a Go backend&lt;/p&gt;
&lt;p&gt;Here is more or less what it looks like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flutter calls into Switf/Kotlin using &lt;a class="link" href="https://docs.flutter.dev/platform-integration/platform-channels" target="_blank" rel="noopener"
 &gt;platform channels&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;API calls are binary encoded protobuf messages&lt;/li&gt;
&lt;li&gt;Native code forwards the raw binary to Go&lt;/li&gt;
&lt;li&gt;Go decodes the protobuf messages and responds&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Go can also call native code via interfaces when it needs to interact with native APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 native["Native Code (Swift, Kotlin)"]
 ui["Flutter UI"]
 go["Go Mobile Backend"]
 
 ui -- Protobuf binary API calls --&gt; native
 native &lt;-- Protobuf forwarded to Go --&gt; go
 go -- Calls to native APIs &lt;/br&gt; (screen time, health) --&gt; native&lt;/pre&gt;&lt;p&gt;vvv CUT vvv&lt;/p&gt;
&lt;h3 id="ui"&gt;UI
&lt;/h3&gt;&lt;p&gt;With Fyne out of the picture, I was left with two solid cross platform options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flutter&lt;/li&gt;
&lt;li&gt;React Native&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ultimately I ended up choosing Flutter for a couple of reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Its was a lot easier to get started with.&lt;/li&gt;
&lt;li&gt;It has a much clearer cross platform story. I didn&amp;rsquo;t see an easy way to do a mobile and desktop app with React Native. I know it&amp;rsquo;s possible with Electron, but as a solo dev the out of the box support for Mac, Windows and Linux in Flutter was just too enticing.&lt;/li&gt;
&lt;li&gt;pub.dev is a great central location for finding Flutter packages and had integrations for Apple and Google health.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I do have some regrets picking Flutter. When it came time to build UI widgets for the plugin system I couldn&amp;rsquo;t find a way to build dynamic UIs in Flutter and it would have been so much easier to just let plugins build React UIs. Aside from that, it&amp;rsquo;s been largely very positive.&lt;/p&gt;
&lt;p&gt;^^^ CUT ^^^&lt;/p&gt;
&lt;h3 id="flutter-to-go-communication"&gt;Flutter to Go Communication
&lt;/h3&gt;&lt;p&gt;As mentioned above, Flutter communicates with Go using Protobuf messages. Protobuf is a MUST here as it allows me to work with nice structs/classes in Go and Dart without having to do a lot of manual marshalling and unmarshalling of JSON objects. I can define my messages in Protobuf and automatically get nice objects in Dart and Go to work with. Communication between Flutter, platform code and Go can only be done with basic data types (strings, binary, booleans, etc) so Protobuf messages are perfect for this use case.&lt;/p&gt;
&lt;p&gt;Defining new platform channels is also a huge chore in Flutter since they need to be defined in three places (Flutter, platform and Go), so I ended up going with a single function that handles all API calls. This function passes a message with a large &lt;code&gt;oneof&lt;/code&gt; block to define the actual API call. It looks something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;CarrotAPI&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;oneof&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Function1API&lt;/span&gt; &lt;span class="n"&gt;function1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Function2API&lt;/span&gt; &lt;span class="n"&gt;function2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Each function call in the &lt;code&gt;oneof&lt;/code&gt; block looks something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Function1API&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Flutter populates the &lt;code&gt;request&lt;/code&gt; part of the message, throws it in the &lt;code&gt;CarrotAPI&lt;/code&gt; object and sends it to Go. Go can tell what function is being called through a switch statement on &lt;code&gt;CarrotAPI.api&lt;/code&gt; and populate the correct response. This is a little clunky, but it&amp;rsquo;s much better than creating new platform channels for every function and saves a lot of work that would otherwise need to be duplicated across Swift and Kotlin.&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t go into specifics on how to implement this. There are &lt;a class="link" href="https://medium.com/flutter-community/using-go-library-in-flutter-a04e3496aa05" target="_blank" rel="noopener"
 &gt;other articles&lt;/a&gt; that do a better job of explaining how this works.&lt;/p&gt;
&lt;h3 id="go-to-swiftkotlin-communication"&gt;Go to Swift/Kotlin Communication
&lt;/h3&gt;&lt;p&gt;This is one area that I struggled with for a long time because this is not documented well in Go. Essentially, to send messages from Go to platform code you need to create a Go interface that is then implemented on the platform side and passed into Go when the Go code is invoked. Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;p&gt;We create a Go interface like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IosMethods&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Screentime&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;SetShields&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;HasScreentimePermissions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Go Mobile generates an objective C or Kotlin interface like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@interface MobileIosMethods : NSObject &amp;lt;goSeqRefInterface, MobileIosMethods&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@property(strong, readonly) _Nonnull id _ref;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- (BOOL)hasScreentimePermissions;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- (BOOL)setShields:(NSData* _Nullable)p0;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@end
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Which we can implement in Swift or Kotlin and pass back into Go:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoScreentime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NSObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MobileIosMethodsProtocol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setShields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;setShieldsFromJSONBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;hasScreentimePermissions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;AuthorizationCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;authorizationStatus&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;approved&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppDelegate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FlutterAppDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UIApplication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;didFinishLaunchingWithOptions&lt;/span&gt; &lt;span class="n"&gt;launchOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UIApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LaunchOptionsKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[...]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;carrotApi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DigitalCarrot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MobileNewAppleMobileAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;GoScreentime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="p"&gt;[...]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Once again, these interfaces only support primitive data types. This is another area where Protobuf could be helpful, but I didn&amp;rsquo;t end up using it since there aren&amp;rsquo;t many calls that need to be made from Go to the platform code.&lt;/p&gt;
&lt;p&gt;More information about this can be found in this &lt;a class="link" href="https://medium.com/@matryer/tutorial-calling-go-code-from-swift-on-ios-and-vice-versa-with-gomobile-7925620c17a4" target="_blank" rel="noopener"
 &gt;excellent article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="how-is-all-of-this-working-out"&gt;How is all of this working out?
&lt;/h2&gt;&lt;p&gt;So far I really like this stack. Working with Go is such a pleasure, so that alone is well worth it to me. Beyond my personal weirdness, this approach has some serious pros and cons.&lt;/p&gt;
&lt;h3 id="the-good"&gt;The Good
&lt;/h3&gt;&lt;h4 id="seamless-integration-with-the-server"&gt;Seamless integration with the server
&lt;/h4&gt;&lt;p&gt;The Digital Carrot sync server is also implemented in go. This makes testing sync a breeze because I can just import the client code directly into my server sync tests. I don&amp;rsquo;t have to build any complicated test harnesses with docker to spin up client and server applications running on different stacks. I can literally just &lt;code&gt;go test&lt;/code&gt; it on my Mac and the tests run in under 20 seconds.&lt;/p&gt;
&lt;h4 id="strong-businesspresentation-layer-boundary"&gt;Strong business/presentation layer boundary
&lt;/h4&gt;&lt;p&gt;This is mostly self explanatory. It&amp;rsquo;s difficult to mix business logic with presentation logic because they are written in different languages. The Flutter component of the app just handles the UI (and some minor platform specific stuff such as requesting permissions) and the Go component handles all of the business logic (communicating with the server, saving data, validation, etc).&lt;/p&gt;
&lt;p&gt;One real benefit is that testing the API contract is trivially easy. All of the business logic is tested in Go, which once again, means I don&amp;rsquo;t have to spin up any complicated UI components to test the app. I can write integration tests in Go and just run them with &lt;code&gt;go test&lt;/code&gt;. One unexpected benefit here has been replay testing. I can test the app manually in the UI, record the API calls that the UI makes and then run those API calls back programmatically. Some UI tests are still required, but they can be very minimal since we can assume that the app&amp;rsquo;s business logic is rock solid.&lt;/p&gt;
&lt;p&gt;This separation of concerns also means that it is really easy to replace the UI with something else in the future. If I wanted a more native experience, I could implement the iOS app in Swift UI and the Android app in Kotlin. This is really nice to have in my back pocket given Google&amp;rsquo;s propensity for killing projects. I can be reasonably sure that Go will never disappear, but who can say what will happen in the wacky world of UI.&lt;/p&gt;
&lt;h4 id="large-collection-of-libraries-to-pull-from"&gt;Large collection of libraries to pull from
&lt;/h4&gt;&lt;p&gt;This architecture means that I get access to the full suite of tools available to Go and Dart. The Dart ecosystem provides just about any utility I might want for interfacing with platform specific permissions and Go has a rich assortment of system programming and networking tools that I can pull from.&lt;/p&gt;
&lt;h4 id="the-business-logic-can-run-over-any-protocol"&gt;The business logic can run over any protocol
&lt;/h4&gt;&lt;p&gt;Putting all of my communication on a network capable interface such as protocol means that the backend can run just about anywhere. This has been hugely advantageous on Windows and Mac. On these platforms the backend runs as a daemon in the background and the UI connects to it via gRPC via sockets or pipes. Digital Carrot needs to continuously run in the background to be able to block programs and websites, so being able to just run the Go daemon on it&amp;rsquo;s own is great since there is no need to load all of the UI cruft into memory. As a result Digital Carrot only needs on the order of 30-60mb of RAM to run in the background.&lt;/p&gt;
&lt;p&gt;While this doesn&amp;rsquo;t help much for Digital Carrot, this could also be an advantage for other types of apps. For example you could create a document editor that can run on a home server or locally on device by just pointing the UI to a different API on the network.&lt;/p&gt;
&lt;h4 id="future-ai-shenanigans"&gt;Future AI shenanigans?
&lt;/h4&gt;&lt;p&gt;I haven&amp;rsquo;t experimented with this yet, but having all of the app&amp;rsquo;s APIs be network capable means that any client can interface with it, such as an AI agent. If I wanted to add AI capabilities into the app I don&amp;rsquo;t need to do some kludgey workaround where the AI clicks around in the app. I can just point the AI at the existing and well documented API.&lt;/p&gt;
&lt;h3 id="the-bad"&gt;The Bad
&lt;/h3&gt;&lt;h4 id="performance"&gt;Performance
&lt;/h4&gt;&lt;p&gt;Every function call requires serializing and deserializing Protobuf messages. This adds a tiny amount of overhead on the order of maybe a few milliseconds. This is totally fine for an app like Digital Carrot, but would be unacceptable for something that is more performance sensitive like a game.&lt;/p&gt;
&lt;h4 id="complexity"&gt;Complexity
&lt;/h4&gt;&lt;p&gt;Adding new business logic is harder. You need to define new Protobuf messages and wire them up in Go. The good news is that this also incentivizes me to think a little more carefully before implementing new functionality. This approach also means that you need to be comfortable with at least two programing languages and carry some extra tooling for compiling Protobuf messages in Dart and Go.&lt;/p&gt;
&lt;h4 id="larger-app-size"&gt;Larger app size
&lt;/h4&gt;&lt;p&gt;Go binaries are big. The whole backend takes up about 53mb uncompressed. Its hard to say how much space the app would save if it was purely written in Dart, but I&amp;rsquo;m willing to bet that it wouldn&amp;rsquo;t be more than an extra megabyte.&lt;/p&gt;
&lt;h3 id="the-ugly"&gt;The Ugly
&lt;/h3&gt;&lt;h4 id="bi-directional-communication"&gt;Bi-directional communication
&lt;/h4&gt;&lt;p&gt;One gotcha that I ran into is that there isn&amp;rsquo;t a great way to make calls from the Go application to Flutter. This is important for notifying when changes were made from sync or for sending notifications. Flutter has some tooling to allow this, but I could never get it to work reliably. In the end I just created a function that runs every second in Flutter and polls the backend for state changes. It&amp;rsquo;s not an elegant or particularly good solution, but it was cheap and easy to implement so I don&amp;rsquo;t imagine I&amp;rsquo;ll be changing it.&lt;/p&gt;
&lt;h4 id="async"&gt;Async
&lt;/h4&gt;&lt;p&gt;This was an issue that I didn&amp;rsquo;t catch until the app was submitted for app review. The reviewer rejected the initial app submission on grounds that they noticed slight freezes in the UI. I don&amp;rsquo;t know what kind of hardware Apple tests on to notice this because it was never noticeable to me, but it lead me down a bit of a rabbit hole. As it turns out, Flutter platform channel calls are not asynchronous by default! This means that any call to Go mobile would block the main Flutter thread and freeze the whole UI. In practice this was only noticeable if the backend had to perform an expensive operation such as fetching data from the web. This lead to some really ugly code where each call to go has to be handed off to a separate go routine that would call a callback to Flutter upon completion. This isn&amp;rsquo;t a big deal in the greater scheme of things, but is a footgun that surprised me at the last minute and was somewhat complicated to fix.&lt;/p&gt;
&lt;h4 id="time-zones-and-networking-bugs"&gt;Time zones and networking bugs
&lt;/h4&gt;&lt;p&gt;There is a long standing bug in Go mobile where the local time is always set to GMT. This caused me no shortage of issues until I finally caught it. The only solution at the time was to pass the time zone code into Go from Swift/Kotlin, which just feels wrong.&lt;/p&gt;
&lt;p&gt;DNS lookup is also &lt;a class="link" href="https://github.com/golang/go/issues/58416#issuecomment-3070442502" target="_blank" rel="noopener"
 &gt;completely broken in Go mobile on iOS&lt;/a&gt; out of the box. It works in the simulator, but as soon as you try to run on a real device it throws all sorts of errors. This requires including the &lt;code&gt;libresolv&lt;/code&gt; package in XCode, which took me days to figure out.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts
&lt;/h2&gt;&lt;p&gt;Overall I am very happy with Go mobile. This wasn&amp;rsquo;t an easy journey. Go mobile is still not used very often. There was a lot that I had to make up as I went, but aside from a few very minor bugs it all works really well.&lt;/p&gt;
&lt;p&gt;I will happily use this architecture in any future mobile apps that I end up making. That said, I wouldn&amp;rsquo;t recommend implementing the entirety of your business logic as default architecture. There are niche cases where this will work amazingly well. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you have a team that already happens to be very comfortable with Go.&lt;/li&gt;
&lt;li&gt;Apps rely heavily on Go network services.&lt;/li&gt;
&lt;li&gt;Apps that require native UIs, but have a lot of shared business logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In most cases people will be better off implementing the parts that Go is best at in Go and handling the rest in Dart, Swift, Kotlin, React Native or whatever your favorite stack is. There are some libraries that Go offers (such as Expr and Goja) that don&amp;rsquo;t have good alternatives in the mobile world and being able to tap into these is incredible powerful.&lt;/p&gt;</description></item></channel></rss>