<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Peter Aleksander Bizjak</title>
    <description>The latest articles on DEV Community by Peter Aleksander Bizjak (@peteralexbizjak).</description>
    <link>https://dev.to/peteralexbizjak</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3236958%2Fd1031892-a760-444c-b722-58c3f9879291.jpg</url>
      <title>DEV Community: Peter Aleksander Bizjak</title>
      <link>https://dev.to/peteralexbizjak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/peteralexbizjak"/>
    <language>en</language>
    <item>
      <title>Shipping JSON over the wire is professional negligence</title>
      <dc:creator>Peter Aleksander Bizjak</dc:creator>
      <pubDate>Fri, 19 Dec 2025 06:59:49 +0000</pubDate>
      <link>https://dev.to/peteralexbizjak/shipping-json-over-the-wire-is-professional-negligence-4ehj</link>
      <guid>https://dev.to/peteralexbizjak/shipping-json-over-the-wire-is-professional-negligence-4ehj</guid>
      <description>&lt;p&gt;&lt;em&gt;I can already see that this article is gonna piss off some folks. Fine by me.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Human-readable data in transit is a decades-old mistake we keep defending out of habit. Not because it’s correct. Not because it’s efficient. Because it’s familiar. JSON is the most familiar of them all, and that’s exactly the problem: we treat familiarity like a guarantee.&lt;/p&gt;

&lt;p&gt;Yes, that's my opening salvo.&lt;/p&gt;

&lt;p&gt;Instead of talking about "my feelings", here is the objective issue: &lt;strong&gt;JSON has no type safety&lt;/strong&gt;. Not the way people mean it when they say "type safety". It has no schema. No enforced contract. No canonical representation. It is a string of bytes that both sides politely agree to interpret the same way... Until one side doesn’t.&lt;/p&gt;

&lt;p&gt;"But we have documentation." Sure. Documentation is a story you tell humans. The wire is where truth lives. And on the wire, JSON does not enforce anything.&lt;/p&gt;

&lt;p&gt;You can document that &lt;code&gt;age&lt;/code&gt; is an integer. Then a client sends &lt;code&gt;"age": "27"&lt;/code&gt; &lt;em&gt;because JavaScript&lt;/em&gt;, and no one notices because you silently coerce it. Until you don’t. Or the server sends &lt;code&gt;null&lt;/code&gt; where the client expected a number. Or the server "helpfully" changes &lt;code&gt;snake_case&lt;/code&gt; to &lt;code&gt;camelCase&lt;/code&gt; during a refactor. Or someone adds a field with the same name but a different meaning because it was "unused anyway". Or the backend returns &lt;code&gt;"status": "ok"&lt;/code&gt; on a failure because the error path is duct-taped and the API gateway is lying for it. All of this is legal JSON. That’s the point. JSON will happily serialize your mistakes. JSON will not care. "It's meant to be simple".&lt;/p&gt;

&lt;p&gt;Using no schema and relying on "flexibility" means that your contracts are always implicit, social, and enforced by good vibes. I'm sorry, but you can't expect to build a serious system on crossed fingers, holding hands and singing praises to the deity of choice.&lt;/p&gt;

&lt;p&gt;This is also why "we’ll just validate it" is not an argument for JSON. &lt;strong&gt;It’s an admission&lt;/strong&gt;. It means you need a second system! And you’ll keep doing it in every client, in every service, in every language. Repeatedly. Forever.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm not talking smack at developers of such validation libraries, in fact, we need strong validation logic anyways, regardless of the contract protocol, but not for the protocol itself. That's my whole argument.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With schema-driven formats, you can still mess up, but you have guardrails that exist outside of tribal memory. There is a schema. There is a canonical encoding. There are compatibility rules that are mechanical, not interpretive. You can’t "accidentally" ship a string where a number belongs without someone, somewhere, screaming early.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is, of course, unless you use a language like JavaScript where types don't exist.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And yes, bytes matter too. Text is expensive in the boring ways: field names repeated, quotes repeated, escapes repeated, parsers doing work they shouldn’t have to do, allocations that multiply under load. For what? So that someone can reach for the comfort blanket: debugging. If the best defense of JSON is "I like reading it with my eyes", I cannot put this mildly, you’re optimizing for the wrong thing, and you've missed the plot entirely. Debugging should be done with tools that &lt;strong&gt;understand the contract&lt;/strong&gt;, not with eyeballs scanning a blob and praying you didn’t miss a comma. We have better tooling for contract-driven protocols than "copy as cURL, pipe to &lt;code&gt;jq&lt;/code&gt;, and squint".&lt;/p&gt;

&lt;p&gt;So here’s the actual recommendation: make a schema format, such as Protocol Buffers, Avro, or another schema-driven system, the canonical representation between machines. Use JSON only where it is genuinely appropriate.&lt;/p&gt;

&lt;p&gt;And if you’re still offended by that: &lt;em&gt;good&lt;/em&gt;. You should be. Not at me, though! Be offended at the fact that we collectively decided "a bag of untyped strings with vibes" is an acceptable foundation for systems we claim are serious. Keep JSON as your duct tape if you want. Just don’t call it architecture. And definitely don’t call it "type safe" with a straight face, and pretend that your cute GraphQL schemas are sufficient.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm not going to tell you to drop everything and get familiar with Protocol Buffers and gRPC, but I'm also not going to tell you not to.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>grpc</category>
      <category>json</category>
    </item>
    <item>
      <title>Vibe Coding ++</title>
      <dc:creator>Peter Aleksander Bizjak</dc:creator>
      <pubDate>Mon, 10 Nov 2025 06:08:09 +0000</pubDate>
      <link>https://dev.to/peteralexbizjak/vibe-coding--bp3</link>
      <guid>https://dev.to/peteralexbizjak/vibe-coding--bp3</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on my &lt;a href="https://blog.bizjak.dev/posts/vibe-coding.html" rel="noopener noreferrer"&gt;blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The arrival of any new technology always creates two camps: those who embrace it and those who reject it. While voices in the middle exist, they’re less vocal and often overlooked. Being reasonable isn’t nearly as attention-grabbing.&lt;/p&gt;

&lt;p&gt;When AI became commercially available in a user-friendly way (think ChatGPT), people quickly split into two camps: one predicted mass job loss within months (though we're years past that now), while the other viewed AI as a technological savior set to resolve every problem and replace humans entirely.&lt;/p&gt;

&lt;p&gt;Turns out... Both camps are right! In Europe and North America, the white-collar job market in 2025 shows elevated unemployment (EU: 6.2%; US hiring at weakest since 2009) and widespread layoffs (e.g., Amazon's 14,000 cuts), driven by a mix of factors. AI does have a role in that. It did accelerate displacement, especially in entry-level roles (40% of employers plan reductions; potential 10-20% unemployment spike), via automation in tech, finance, and admin. It's amplifying vulnerability, but not the primary driver yet. Dominant causes include recession signals, slowing growth, high borrowing costs, inflation, and US tariffs hitting Europe, displacing cca. 1.6M jobs globally. AI exacerbates but trails these structural issues.&lt;/p&gt;

&lt;p&gt;So that means that people are losing jobs, and/or not being able to get them, and that higher-ups have managed to find a solution to their problem by cutting down costs of labor and replacing it with AI. But then again, being a voice in the middle is boring. Why am I even trying to be factual here and not rage-bait for clicks? Ugh, silly me!&lt;/p&gt;

&lt;p&gt;When AI-assisted coding became attainable to developers via IDEs such as Cursor, productivity jumped immensely. Even in the earliest stages, we all knew that software engineering was going to change forever. Before IDEs like Cursor, you had to copy-paste code from your text editor to ChatGPT/Cursor chatbot, and  hope you didn’t exceed the context window. Nowadays, you can instruct a model to review your codebase, make a plan, align on requirements with you in a collaborative fashion, and execute it.&lt;/p&gt;

&lt;p&gt;At that point, we, once again, ended up with two camps. On one end, you had so-called “vibe coders”, people who blindly trust AI. On the other hand, hardcore AI rejectionists. The puritans of software engineering, if you wish. In this case, I can’t say that both camps are justified in their own way, but the truth is that the job of software engineering is somewhere in between. There are tasks that you can absolutely delegate to AI. And there are tasks that you simply can’t. Not because AI is not good enough, or will not get good enough in the future, but because you cannot replace the human element in certain tasks and jobs with a machine.&lt;/p&gt;

&lt;p&gt;Again, what’s up with all that middle-ground exasperation? Make up your mind!&lt;/p&gt;

&lt;p&gt;Humans tend towards comfort over harsh conditions for life. That’s why we innovate and adapt the environment to our liking. So it’s not a surprise that vibe coding, the practice of using AI as a replacement for doing the hard work of thinking, reasoning, and working, is the default preference. The only problem with AI-written code is the fact that AI models are prone to hallucinations, bad/obsolete training data, and often miss the aforementioned human decision-making and forward-thinking component. The result? Bedraggled code.&lt;/p&gt;

&lt;p&gt;LinkedIn saw the emergence of “Vibe coding cleanup specialists”. I’m pretty sure there is a demand for such workers. Because if vibe coding is the future of software engineering, then at some point, there will be an emergence of a movement that wants to adhere to best practices in AI-written code, including writing tests and following battle-tested design patterns. And that’s the influence of the AI-rejectionists/manually-written-code puritans camp. Humans have been working with technology for far longer than AI has been around. You can read all about tech, and you can ingest all the knowledge in the world, but you’ll never be able to beat experience. You’ll never be able to beat intuition born through hard work and getting burned by mistakes.&lt;/p&gt;

&lt;p&gt;The idea will be peddled as self-healing AI-written code.&lt;/p&gt;

&lt;p&gt;Code is supposed to be read; writing it is easy. Code is supposed to execute and perform necessary functions, and in order for you to know that’s the case, you need to test it. At some point, you’ll have to make changeovers, so wouldn’t it help if code were designed in a way where modifications can be gradual, incremental, and isolated? So this is where I’m making my stand. AI-written code will have to follow certain principles of “clean” design (which I know is a triggering word for some), and it will have to be well-tested and validated before being given to humans for review.&lt;/p&gt;

&lt;p&gt;All of this will demand skills and a deep understanding of the technology one works with. Yes, AI can write a lot of code very fast, and most likely, this code is better than what you would write manually. The only metric of how “good” a code is, is whether or not it passes tests (which you still have to orchestrate and understand how to write), and whether or not it’s written in a way that’s easy to understand and follows battle-tested design patterns. This is where humans come in. It’s your experience, it’s your talent, it’s your skills, all put in a line. How well can you communicate, how clear can your instructions be, how well can you predict future edge-cases... No amount of AI assistance can help you with that. This takes years of practice, which nobody (and nothing) can replace.&lt;/p&gt;

&lt;p&gt;Keep shipping.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>ai</category>
      <category>software</category>
    </item>
    <item>
      <title>Golang for FFI Flutter Plugins</title>
      <dc:creator>Peter Aleksander Bizjak</dc:creator>
      <pubDate>Sun, 01 Jun 2025 17:02:36 +0000</pubDate>
      <link>https://dev.to/peteralexbizjak/golang-for-ffi-flutter-plugins-5e4k</link>
      <guid>https://dev.to/peteralexbizjak/golang-for-ffi-flutter-plugins-5e4k</guid>
      <description>&lt;p&gt;If you've ever developed applications with Flutter, you've probably pulled a package from &lt;code&gt;pub.dev&lt;/code&gt; that uses FFI (foreign function interface). These packages call native C APIs and handle all the basic memory management tasks. I'll not delve too deep into details on C interop using &lt;code&gt;dart:ffi&lt;/code&gt; in this article, for that, you should simply read the &lt;a href="https://dart.dev/interop/c-interop" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Programming languages you'd typically associate with the creation of such packages are, well, kinda obviously, C and C++. However, the conditions for developing an FFI Flutter plugin are fairly straightforward: the language must support creating libraries that can be compiled into binary format, these binaries must be compatible with the target platform, and they need to support C/C++ interop. Technically speaking, the C interop is the important one. You can mix C and C++, but you have to be &lt;a href="https://isocpp.org/wiki/faq/mixing-c-and-cpp" rel="noopener noreferrer"&gt;aware of what you're doing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Besides the usual suspects, Rust comes up in the discussion quite a bit. Funny enough, many FFI-based packages use Rust nowadays. Since I'm not experienced with Rust (beyond being able to write a hello world program and read Rust code with the help of AI), I cannot comment much on the viability of the language for that particular task. As far as I'm aware, &lt;a href="https://github.com/fzyzcjy/flutter_rust_bridge" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_rust_bridge&lt;/code&gt;&lt;/a&gt; is a popular choice. Other than C, C++, and Rust, I would imagine that Zig would be a good candidate as well. However, the language and its community are fairly small (as far as I know, Bun.sh is the only Zig's flagship project), so I'm not sure how many Flutter developers even know about it, let alone use it.&lt;/p&gt;

&lt;p&gt;So what about Go?&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;First time I heard of Go was in 2019, and at the time, I was not interested in system programming. Sure, as a student researcher, I worked extensively with C (and to a certain extent with C++), but the language never really drew my attention until sometime in 2022 when I was evaluating an alternative to Nest.js-based microservice. The reason was mostly memory footprint and ease of deployment. I spent a few hours going through the language tour, and found the language extremely easy to work with.&lt;/p&gt;

&lt;p&gt;But I only saw the usability of Go as an alternative to Python for scripting/CLI application development, and obviously for backend development. Especially when it comes to gRPC (but that's a story for another time). I never looked at Go as an option for the development of FFI Flutter plugins, until sometime last year, I got acquainted with &lt;a href="https://tailscale.com" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt;. Tailscale has built its products around Go, including mobile applications. For reference, I'd strongly encourage you to check the source code of their &lt;a href="https://github.com/tailscale/tailscale-android" rel="noopener noreferrer"&gt;Android client&lt;/a&gt; and the &lt;a href="https://github.com/tailscale/libtailscale/tree/main/swift" rel="noopener noreferrer"&gt;Swift library&lt;/a&gt; inside the main &lt;code&gt;libtailscale&lt;/code&gt; repository. &lt;em&gt;So, somebody did it... A successful company uses Golang for its clients.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment
&lt;/h2&gt;

&lt;p&gt;The big picture of my experiment is simply this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an FFI Flutter plugin that uses Golang, supporting Android and iOS&lt;/li&gt;
&lt;li&gt;Create a simple proof-of-concept application that's not a dumb hello world app or a default "sum of two numbers" example.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that in mind, I set out to develop an RSS reader application called &lt;a href="https://github.com/sunderee/rss-it" rel="noopener noreferrer"&gt;RSSit&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before doing anything, let's go over prerequisites:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We need to make sure that Golang supports C interop&lt;/li&gt;
&lt;li&gt;Can produced binaries be used in Android or iOS applications, and what build tool will be used?&lt;/li&gt;
&lt;li&gt;How to efficiently pass (what might end up being) a large amount of data between Go and Dart?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So let's tackle those one by one.&lt;/p&gt;

&lt;h3&gt;
  
  
  C, Go, and Cgo
&lt;/h3&gt;

&lt;p&gt;There's a thing called Cgo. Cgo enables the creation of Go packages that call C code. I'm not going to discuss Cgo in detail, as their &lt;a href="https://go.dev/wiki/cgo" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; should be enough for anyone to get started.&lt;/p&gt;

&lt;p&gt;One important note here: because we'll be using Cgo and Go's built in compiler, there's no reason to rely on Clang. The only file left from the default example was the header file declaring exports of compiled libraries, something used by &lt;code&gt;ffigen&lt;/code&gt; package to produce Dart bindings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native libraries in Android and iOS
&lt;/h3&gt;

&lt;p&gt;For Android, we'll need to use NDK and produce a dynamic library for each CPU architecture. For iOS, we'll produce static libraries + corresponding header files.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Wait... How do you know that?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Simple. What's the difference between shared and dynamic libraries? Statically linked libraries copy all the library modules used in the program into the final executable image, while dynamically linked libraries load the external shared libraries into the program, binding them to the final image.&lt;/p&gt;

&lt;p&gt;Because we're dealing with Cgo, setting &lt;code&gt;CGO_ENABLED=1&lt;/code&gt; is going to be a must. This environment variable controls whether the Cgo tool is used during the build process (which, yes, it is). It determines if Go code can interact with C code and libraries (which, yes, it has to).&lt;/p&gt;

&lt;p&gt;Android apps load native libraries dynamically at runtime. iOS applications, on the other hand, are distributed as single bundles, meaning all code has to be included.&lt;/p&gt;

&lt;p&gt;When building the Go library, deciding between static/dynamic libraries is handled using &lt;a href="https://pkg.go.dev/cmd/go#hdr-Build_modes" rel="noopener noreferrer"&gt;&lt;code&gt;buildmode&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For static libraries, use &lt;code&gt;-buildmode=c-archive&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For dynamic libraries, use &lt;code&gt;-buildmode=c-shared&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Android, multiple CPU architectures have to be supported (ARMv7, ARM64, x86, x86_64). You set the architecture using &lt;code&gt;GOARCH&lt;/code&gt; and the operating system using the &lt;code&gt;GOOS&lt;/code&gt; environment variables. On top of that, the &lt;code&gt;CC&lt;/code&gt; environment variable should link to Android NDK compiler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="c"&gt;# Library configuration&lt;/span&gt;
&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rss_it_library"&lt;/span&gt;
&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lib&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"../prebuild/Android"&lt;/span&gt;

&lt;span class="c"&gt;# Build mode configuration&lt;/span&gt;
&lt;span class="nv"&gt;BUILD_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;release&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;GO_BUILD_FLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-trimpath -ldflags=-s"&lt;/span&gt;

&lt;span class="c"&gt;# Detect OS for NDK path&lt;/span&gt;
&lt;span class="nv"&gt;NDK_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"27.0.12077973"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OSTYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"linux-gnu"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Android/Sdk"&lt;/span&gt;
  &lt;span class="nv"&gt;NDK_PLATFORM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"linux-x86_64"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OSTYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"darwin"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Library/Android/sdk"&lt;/span&gt;
  &lt;span class="nv"&gt;NDK_PLATFORM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"darwin-x86_64"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OSTYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"msys"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OSTYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"win32"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class="p"&gt;//\\//&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Android/Sdk"&lt;/span&gt;
  &lt;span class="nv"&gt;NDK_PLATFORM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"windows-x86_64"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Unsupported operating system: &lt;/span&gt;&lt;span class="nv"&gt;$OSTYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check if NDK exists&lt;/span&gt;
&lt;span class="nv"&gt;NDK_BIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ANDROID_HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/ndk/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NDK_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/toolchains/llvm/prebuilt/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NDK_PLATFORM&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NDK_BIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Android NDK not found at &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NDK_BIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Please install Android NDK &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NDK_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; or update the script with your NDK path"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Using Android NDK at: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NDK_BIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; for Android..."&lt;/span&gt;

&lt;span class="c"&gt;# Function to build for a specific architecture&lt;/span&gt;
build_for_arch&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local arch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;goarch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;cc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;dir_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;goarm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building for &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;arch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;

  &lt;span class="c"&gt;# Create output directory&lt;/span&gt;
  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;dir_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c"&gt;# Set environment variables&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;android
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$goarch&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$goarm&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOARM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$goarm&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NDK_BIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;cc&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c"&gt;# Build the library&lt;/span&gt;
  go build &lt;span class="nv"&gt;$GO_BUILD_FLAGS&lt;/span&gt; &lt;span class="nt"&gt;-buildmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;c-shared &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;dir_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.so"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

  &lt;span class="c"&gt;# Remove header file&lt;/span&gt;
  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;dir_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.h"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Successfully built for &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;arch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Build for all architectures&lt;/span&gt;
build_for_arch &lt;span class="s2"&gt;"ARMv7"&lt;/span&gt; &lt;span class="s2"&gt;"arm"&lt;/span&gt; &lt;span class="s2"&gt;"armv7a-linux-androideabi21-clang"&lt;/span&gt; &lt;span class="s2"&gt;"armeabi-v7a"&lt;/span&gt; &lt;span class="s2"&gt;"7"&lt;/span&gt;
build_for_arch &lt;span class="s2"&gt;"ARM64"&lt;/span&gt; &lt;span class="s2"&gt;"arm64"&lt;/span&gt; &lt;span class="s2"&gt;"aarch64-linux-android21-clang"&lt;/span&gt; &lt;span class="s2"&gt;"arm64-v8a"&lt;/span&gt;
build_for_arch &lt;span class="s2"&gt;"x86"&lt;/span&gt; &lt;span class="s2"&gt;"386"&lt;/span&gt; &lt;span class="s2"&gt;"i686-linux-android21-clang"&lt;/span&gt; &lt;span class="s2"&gt;"x86"&lt;/span&gt;
build_for_arch &lt;span class="s2"&gt;"x86_64"&lt;/span&gt; &lt;span class="s2"&gt;"amd64"&lt;/span&gt; &lt;span class="s2"&gt;"x86_64-linux-android21-clang"&lt;/span&gt; &lt;span class="s2"&gt;"x86_64"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Android build complete. Libraries are in &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On iOS, we only need to support two architectures (x86_64 and ARM64), we use Xcode command line tools and the Clang compiler (set to &lt;code&gt;CC&lt;/code&gt; environment variable). I rely on &lt;code&gt;lipo&lt;/code&gt; to create a universal library, which is something you need to support iPhone Simulators.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="c"&gt;# Check if running on macOS&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OSTYPE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"darwin"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: This script must be run on macOS"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Library configuration&lt;/span&gt;
&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"rss_it_library"&lt;/span&gt;
&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lib&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"../prebuild/iOS"&lt;/span&gt;

&lt;span class="c"&gt;# Build mode configuration&lt;/span&gt;
&lt;span class="nv"&gt;BUILD_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;release&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;GO_BUILD_FLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-trimpath -ldflags=-s"&lt;/span&gt;

&lt;span class="c"&gt;# Check for Xcode tools&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; xcrun &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Xcode command line tools not found"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Please install Xcode command line tools with: xcode-select --install"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; for iOS..."&lt;/span&gt;

&lt;span class="c"&gt;# Minimum iOS version&lt;/span&gt;
&lt;span class="nv"&gt;MIN_IOS_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"11.0"&lt;/span&gt;

&lt;span class="c"&gt;# Function to build for a specific architecture&lt;/span&gt;
build_for_arch&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;sdk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;goarch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;carch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;device_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building for &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;device_type&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;carch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)..."&lt;/span&gt;

  &lt;span class="c"&gt;# Create output directory&lt;/span&gt;
  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sdk&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;carch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c"&gt;# Get SDK path&lt;/span&gt;
  &lt;span class="nv"&gt;SDK_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;xcrun &lt;span class="nt"&gt;--sdk&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sdk&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--show-sdk-path&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;# Set target triple&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$sdk&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"iphoneos"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;carch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-apple-ios&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIN_IOS_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;carch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-apple-ios&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIN_IOS_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-simulator"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;

  &lt;span class="c"&gt;# Find Clang compiler&lt;/span&gt;
  &lt;span class="nv"&gt;CLANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;xcrun &lt;span class="nt"&gt;--sdk&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sdk&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--find&lt;/span&gt; clang&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;# Set environment variables&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ios
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;goarch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CLANG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -target &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -isysroot &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SDK_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c"&gt;# Build the library&lt;/span&gt;
  go build &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GO_BUILD_FLAGS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-buildmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;c-archive &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sdk&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;carch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.a"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

  &lt;span class="c"&gt;# Remove header file&lt;/span&gt;
  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sdk&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;carch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.h"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Successfully built for &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;device_type&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;carch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Build for each platform&lt;/span&gt;
build_for_arch &lt;span class="s2"&gt;"iphonesimulator"&lt;/span&gt; &lt;span class="s2"&gt;"amd64"&lt;/span&gt; &lt;span class="s2"&gt;"x86_64"&lt;/span&gt; &lt;span class="s2"&gt;"iOS Simulator"&lt;/span&gt;
build_for_arch &lt;span class="s2"&gt;"iphonesimulator"&lt;/span&gt; &lt;span class="s2"&gt;"arm64"&lt;/span&gt; &lt;span class="s2"&gt;"arm64"&lt;/span&gt; &lt;span class="s2"&gt;"iOS Simulator (Apple Silicon)"&lt;/span&gt;
build_for_arch &lt;span class="s2"&gt;"iphoneos"&lt;/span&gt; &lt;span class="s2"&gt;"arm64"&lt;/span&gt; &lt;span class="s2"&gt;"arm64"&lt;/span&gt; &lt;span class="s2"&gt;"iOS Device"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"iOS build complete. Libraries are in &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Ask if user wants to create a universal library&lt;/span&gt;
&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Create universal library for simulators? (y/n): "&lt;/span&gt; create_universal
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;create_universal&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"y"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Creating universal library for simulators..."&lt;/span&gt;

  &lt;span class="c"&gt;# Create universal directory&lt;/span&gt;
  &lt;span class="nv"&gt;UNIVERSAL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/iphonesimulator/universal"&lt;/span&gt;
  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UNIVERSAL_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c"&gt;# Create universal binary&lt;/span&gt;
  lipo &lt;span class="nt"&gt;-create&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/iphonesimulator/x86_64/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.a"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREBUILD_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/iphonesimulator/arm64/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.a"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-output&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UNIVERSAL_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.a"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Universal library created at: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UNIVERSAL_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.a"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, in both cases &lt;code&gt;GO_BUILD_FLAGS="-trimpath -ldflags=-s"&lt;/code&gt; is used to reduce the binary size by trimming paths and stripping debugging symbols.&lt;/p&gt;

&lt;h3&gt;
  
  
  Efficient data serialization/deserialization
&lt;/h3&gt;

&lt;p&gt;Probably the last thing you wanna do is marshal the response to a JSON string, and send it over, only to be decoded on the Dart side. Sure, if your JSON looks like a simple &lt;code&gt;{"status": "success"}&lt;/code&gt;, but RSS/Atom/JSON feeds, especially if [arsing] a large number of them, will produce a massive JSON object.&lt;/p&gt;

&lt;p&gt;Not just for this purpose, but in general, I'm against the idea of using human-readable data format for data in motion. There's absolutely no reason for us to rely on JSON in 2025, when we have efficient protocols, like protocol buffers, that use a binary format (aka the &lt;em&gt;wire format&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Let's do a short Q&amp;amp;A with some of the questions I've been asked in regards to the opinion above.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"Oh, but how do you test your backends in that case?"&lt;/em&gt; If you're only manually testing your backend code with Postman, Insomnia, or whatever HTTP client out there, you're doing something wrong. Your backend should contain automated tests, and (of course) manual QA. With that being said, Postman and Insomnia allow you to work with gRPC, and they will render the response in a human-readable format. Same with &lt;code&gt;grpcurl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"But what about web application? gRPC uses HTTP/2, which is not yet supported!"&lt;/em&gt; Just make a proxy.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"But JSON is a de facto standard, we can't just expect everyone to move over to something else?"&lt;/em&gt; XML was a standard before JSON. Many of you who grew up only knowing JSON probably have no clue what SOAP is. Point being, standards, consensuses, and trends change.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"But there's too much overhead associated with protocol buffers, I need so many extra steps!"&lt;/em&gt; The only "extra step" is writing a common contract between clients and servers, and using a &lt;code&gt;protoc&lt;/code&gt; compiler to generate client/server code. Besides, speaking of overhead: let's talk about data serialization/deserialization, having to generate OpenAPI specifications, perhaps use a code generator to produce client code from the .json specification file, and don't get me started on request/response validation, streaming requests, SSE, ...&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"But it's a whole new language to learn!"&lt;/em&gt; Web developers have no problem learning Prisma, but will cry about protocol buffers, whose syntax is much easier to get a hang on (took me a few minutes to get a grasp of it).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use of goroutines
&lt;/h3&gt;

&lt;p&gt;I was initially worried about whether or not my heavy reliance on goroutines would have any impact and/or would break when used in Flutter FFI, but after doing a bit of digging, I realized that goroutines are compiled into lightweight threads managed by Go's runtime, embedded in the library.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are my main takeaways?
&lt;/h2&gt;

&lt;p&gt;During this experiment, I learned a few valuable lessons. If I had to produce an executive summary:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go is a viable language for the development of FFI Flutter plugins&lt;/li&gt;
&lt;li&gt;Using protocol buffers pays off, as it gives me a common contract between Go and Dart, and the efficient data serialization/deserialization is worth the setup&lt;/li&gt;
&lt;li&gt;I believe that my setup is easily replicable for any use case&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As I mentioned in one of my long Twitter threads, the argument of "pick the right tool for the job", which is something I completely stand behind, in my opinion easily applies here. I don't think this experiment should be taken as "everyone needs to start using Go for the development of FFI Flutter plugins", but rather "here's an option".&lt;/p&gt;

&lt;p&gt;I'm pretty sure there's a lot of great libraries developed in Go (or there's a lot of great engineers who could produce great libraries in Go). Opening the doors to using Go, and its extensive ecosystem, in Flutter application development, has a bunch of positive implications.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>ffi</category>
      <category>go</category>
      <category>dart</category>
    </item>
  </channel>
</rss>
