<?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: Utakata Code</title>
    <description>The latest articles on DEV Community by Utakata Code (@code_utakata).</description>
    <link>https://dev.to/code_utakata</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4233386%2Ff564bb28-e451-4b7f-a893-552fe60cc4ab.jpeg</url>
      <title>DEV Community: Utakata Code</title>
      <link>https://dev.to/code_utakata</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/code_utakata"/>
    <language>en</language>
    <item>
      <title>How I got the Flutter toolchain building apps on an Android phone</title>
      <dc:creator>Utakata Code</dc:creator>
      <pubDate>Sun, 08 Nov 2026 09:59:40 +0000</pubDate>
      <link>https://dev.to/code_utakata/how-i-got-the-flutter-toolchain-building-apps-on-an-android-phone-4p3b</link>
      <guid>https://dev.to/code_utakata/how-i-got-the-flutter-toolchain-building-apps-on-an-android-phone-4p3b</guid>
      <description>&lt;p&gt;For the past few months I've been building a Flutter IDE that runs on an Android phone. The Flutter SDK, the Dart VM, a JDK and Gradle all run on the phone itself, and the APK comes out of the phone too.&lt;/p&gt;

&lt;p&gt;It's called Utakata Studio Mobile IDE, and it just went into open testing on Google Play. This post is about the part I found most interesting to solve: getting a toolchain that was written for desktop Linux, macOS and Windows to run on Android.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Flutter SDK is mostly data
&lt;/h2&gt;

&lt;p&gt;The first thing that made this feel possible was looking at what a Flutter SDK actually contains. Most of it doesn't care which OS it runs on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the framework itself is Dart source,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flutter_tools&lt;/code&gt; ships as a kernel snapshot,&lt;/li&gt;
&lt;li&gt;the VM snapshots, ICU data, the patched SDK and &lt;code&gt;sky_engine&lt;/code&gt; are plain files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What does care are the host binaries. For a phone, that list is short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Dart SDK (&lt;code&gt;dart&lt;/code&gt;, &lt;code&gt;dartaotruntime&lt;/code&gt;, the frontend server),&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gen_snapshot&lt;/code&gt; for release and profile builds,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;impellerc&lt;/code&gt; (every app needs it, because the framework's own shaders are compiled with it),&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;font-subset&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flutter_tester&lt;/code&gt; for &lt;code&gt;flutter test&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the plan became: build those for Android arm64, and reuse everything else from the official Linux SDK as-is. The Dart SDK builds for Android with its own build script (&lt;code&gt;--os android --arch arm64&lt;/code&gt;), and the engine's host tools build with &lt;code&gt;gn --android --android-cpu arm64&lt;/code&gt; using Android's default toolchain. Everything else is copied over from the official linux-x64 SDK, minus the ELF files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Telling flutter_tools that Android is Linux
&lt;/h2&gt;

&lt;p&gt;Run on a phone, the Dart VM reports &lt;code&gt;Platform.operatingSystem == "android"&lt;/code&gt;. &lt;code&gt;flutter_tools&lt;/code&gt; has never expected to be &lt;em&gt;hosted&lt;/em&gt; on Android, so it gets confused quickly.&lt;/p&gt;

&lt;p&gt;The fix turned out to be a small patch: report &lt;code&gt;android&lt;/code&gt; as &lt;code&gt;linux&lt;/code&gt;, map the host platform to &lt;code&gt;linux_arm64&lt;/code&gt;, and point the &lt;code&gt;gen_snapshot&lt;/code&gt; lookup at the arm64 build. After that, &lt;code&gt;flutter_tools&lt;/code&gt; mostly behaves as if it were running on an arm64 Linux machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can't execute files you download (or create)
&lt;/h2&gt;

&lt;p&gt;This was the part that's specific to Android. Since Android 10, an app targeting a recent SDK can't &lt;code&gt;exec&lt;/code&gt; files in its own data directory. It's part of Android's W^X policy, and adding a directory to &lt;code&gt;PATH&lt;/code&gt; doesn't help.&lt;/p&gt;

&lt;p&gt;The only place an app can run native code from is its native library directory. So every executable is packaged inside the APK as &lt;code&gt;lib&amp;lt;name&amp;gt;.so&lt;/code&gt;, the APK is built with legacy packaging so the files are extracted to disk, and the SDK's &lt;code&gt;bin/dart&lt;/code&gt; and friends are links to those files.&lt;/p&gt;

&lt;p&gt;That raised two more problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Shell scripts: &lt;code&gt;bin/flutter&lt;/code&gt; and &lt;code&gt;gradlew&lt;/code&gt; are scripts, and they can't be exec'd either. Instead of shipping them as fake &lt;code&gt;.so&lt;/code&gt; files, the IDE starts them through &lt;code&gt;/system/bin/sh&lt;/code&gt;. That needed small patches in two places: where &lt;code&gt;flutter_tools&lt;/code&gt; launches &lt;code&gt;gradlew&lt;/code&gt;, and where Flutter's Gradle plugin calls back into &lt;code&gt;bin/flutter&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finding the SDK from the VM: the Dart VM uses &lt;code&gt;/proc/self/exe&lt;/code&gt; to work out where it lives, and that points at &lt;code&gt;…/lib/arm64/libmide_dart.so&lt;/code&gt;, not at the SDK. Tools like &lt;code&gt;dart run&lt;/code&gt; and &lt;code&gt;build_runner&lt;/code&gt; then can't find the SDK. A patch to the VM makes it return the path it was &lt;em&gt;invoked&lt;/em&gt; with (taken from &lt;code&gt;AT_EXECFN&lt;/code&gt;), but only when that path is the same file as &lt;code&gt;/proc/self/exe&lt;/code&gt;. It touches nothing under &lt;code&gt;runtime/vm/&lt;/code&gt;, so the snapshot hash doesn't change.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A JDK for Gradle
&lt;/h2&gt;

&lt;p&gt;Android builds still go through Gradle and the Android Gradle Plugin, so the phone needs a JDK. I cross-compile OpenJDK 21 for &lt;code&gt;aarch64-linux-android&lt;/code&gt; (API 28) with the NDK, using patches from the termux-packages project for the Bionic-specific parts. &lt;code&gt;aapt2&lt;/code&gt; gets the same treatment.&lt;/p&gt;

&lt;p&gt;It's a full server JVM (C1 and C2), around 270 MB unpacked. On a recent phone it runs Gradle comfortably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building without the internet
&lt;/h2&gt;

&lt;p&gt;A first Gradle build normally downloads a lot. On a phone, on mobile data, that's not great, so the IDE ships the Maven artifacts and Gradle distribution it needs for a release build, plus the pub packages used by the templates, and points Gradle at that local repository first. Packages that aren't bundled are still fetched from pub.dev and Maven when you're online.&lt;/p&gt;

&lt;p&gt;The bundled parts add up: what a release build needs (the Maven artifacts, Gradle and lint) is around 440 MB compressed, and it passes 700 MB once you add the debug engine, common plugins and code generation. So only what a build needs is installed with the app, as Play asset packs at install time: the Flutter SDK, those release build essentials and the NDK. The debug engine, common plugins and code-generation packages are downloaded the first time you need them, straight from where they're published (Google's Maven repository and pub.dev).&lt;/p&gt;

&lt;h2&gt;
  
  
  Hot reload inside the IDE
&lt;/h2&gt;

&lt;p&gt;The preview is the part I like most. Your app runs inside the IDE, in a separate process, on the official Flutter debug engine, which ships inside the app itself. Saving a Dart file triggers a normal hot reload, and the app keeps its state.&lt;/p&gt;

&lt;p&gt;On a Galaxy Z Fold7, a neon shooter demo built in about 6 seconds when I pressed Run, showed up in the preview in 0.8 seconds, and hot reloaded in 0.72 seconds after I changed its bullet color. The score stayed where it was.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bug you only hit when Dart runs on Android
&lt;/h2&gt;

&lt;p&gt;One more that I found recently. In a project that uses Drift (which depends on &lt;code&gt;sqlite3&lt;/code&gt; and its build hook), &lt;code&gt;dart run&lt;/code&gt; stopped before running anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build input for sqlite3 contains errors - No value was provided for 'config.extensions.code_assets.android'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cause wasn't in the hook. When &lt;code&gt;dart run&lt;/code&gt; builds native assets for the host, dartdev passes a macOS-specific config but nothing for Android, because nobody runs the Dart SDK &lt;em&gt;on&lt;/em&gt; Android. With the host being Android, the hook runner's input check fails before the hook even starts.&lt;/p&gt;

&lt;p&gt;Passing an Android code config for the host fixed it, and &lt;code&gt;sqlite3&lt;/code&gt; loads on the phone now. I patched the bundled SDK, and the fix is rolling out through the test builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;A few things to know before trying it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it needs a 64-bit Android device on Android 9 or later, with at least 4 GB of RAM (8 GB is nicer),&lt;/li&gt;
&lt;li&gt;the toolchain takes several GB of storage,&lt;/li&gt;
&lt;li&gt;some native plugins (camera, location and so on) don't work in the in-IDE preview, so you install the APK to try them,&lt;/li&gt;
&lt;li&gt;it's an open test, and things will break.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you're curious, the open test is on Google Play and anyone can join:&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=code.utakata.mide" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=code.utakata.mide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bug reports and "this didn't work on my phone" messages help the most right now. If you've run into any of these problems in your own projects, I'm curious how you dealt with them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Flutter and Dart are trademarks of Google LLC. This project is not affiliated with or endorsed by Google.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>android</category>
      <category>dart</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
