Developing and debugging Flutter apps for iOS without a Mac

Gabriel Rodríguez
Flutter Community
Published in
8 min readJan 10, 2019

2022 UPDATE:

It is now possible to use a macOS VM to run and debug Flutter apps on iOS devices, thanks to usbfluxd which is a useful tool that redirects iOS devices connected via USB from the host to the guest (or any two machines). I wrote a script that automates all commands needed to do this. I then use VSCode directly on the VM.

However, since using a VM requires beefier computers with enough RAM for both the host and guest, I don’t think this is a replacement for what this former tutorial achieved, which worked on 4 GB of RAM and maybe even 2.

On Linux, I like using OSX-KVM, because I prefer Virt Manager, and my script is written for that specific setup (if you have a different setup, you’ll need to change the IP addresses). Sosumi is a snap package that automates all VM setup, if you use Ubuntu or like Snaps. Docker-OSX is another option, and has a good Discord community so it’s probably the easiest one to get support to troubleshoot any issues.

As for Windows, I don’t have any VM recommendations. I’m not sure if usbfluxd runs on Windows to begin with, so this may only be possible on Linux now.

If these instructions aren’t specific enough to you, or you think it would be of value to write a tutorial to replicate my setup, please let me know in the comments section of https://gist.github.com/gabrc52/2960d9905b835bdf750fc54f983d0412

Update: Flutter 1.17.0 now uses XCode instead of libimobiledevice to list devices. At the moment, you can downgrade to Flutter 1.12.13+hotfix.9, or use the new Codemagic feature that allows you to remote control a Mac VM, and run a virtual device there (https://blog.codemagic.io/remote-access-to-virtual-mac-build-machine/)

— — —

This tutorial is now outdated, but it is still available for reference purposes, since you can still build apps this way and install them on iOS devices. What won’t work anymore from here is debugging on iOS. It’s still technically possible to debug (hot reload and hot restart) without ever touching macOS. If you are interested in working on making a new method to do exactly this, there are 2 ways I can think of:

  • if anyone makes a pull request to Flutter that makes it use libimobiledevice tools instead of XCode tools (see https://github.com/flutter/flutter/issues/56511)
  • maybe you could manually forward the ports via iproxy and manually flutter attach to the port.

What is more, Flutter doesn’t allow launching a debug app from the home screen on iOS 14+ so this would be another change to fix to achieve what used to be achievable.

I don’t have the availability to do this, since what I now use is OSX-KVM and usbfluxd, so I do touch a Mac now, even if it’s not a bare metal one (see the 2022 update at the top of this post).

— — —

It is possible to hot reload and hot restart your Flutter app on your iOS devices without having to use a Mac just like you can with Android devices! Here’s how:

Get a debug build of your app running on your iOS device

You could do that with a Mac (or Hackintosh, or VM), but since we don’t have access to a macOS machine we can use one remotely via Codemagic or Travis CI — completely free! (as long as your project is on a GitHub, Bitbucket or GitLab repository).

Recommended: Building the app with Codemagic

Codemagic is recommended as it requires less setup and builds pretty fast

First, create an account or sign in to codemagic.io.

Then, click the settings (gear) icon next to your app. Scroll down and click on “Build”. Make sure Mode is set to Debug, and select iOS under Build for platforms.

By default, Codemagic will test your app. Disable this feature unless you want to use it. (Thanks to Near Cz for the feedback!)

After that, build the app (Start your first build).

New: now Codemagic lets you remote control the macOS virtual machine it uses to build your app. From there, you can run a simulator, test your app, and configure it from XCode. See https://blog.codemagic.io/remote-access-to-virtual-mac-build-machine/

Codemagic will send you an .app file via email.

Rename it so that it ends with .zip.

Extract it, and you’ll get a folder called Runner.app.

Create a folder called Payload and place Runner.app there.

Finally, compress the folder called Payload — this will be your IPA file (you may rename it to .ipa).

To continue, skip to Installing and running the app.

Alternative: Building the app with Travis CI

Note: the free version of Travis CI only supports public GitHub repositories.

You’ll need to create an account on Travis CI and let it access your GitHub account.

Then, create .travis.yml on the root of your project with the following contents:

os: osx 
language: generic
before_script:
- brew update
- brew install --HEAD usbmuxd
- brew unlink usbmuxd
- brew link usbmuxd
- brew install --HEAD libimobiledevice
- brew install ideviceinstaller
- brew install ios-deploy
- git clone https://github.com/flutter/flutter.git -b beta --depth 1
script:
- flutter/bin/flutter build ios --debug --no-codesign
cache:
directories:
- $HOME/.pub-cache
before_deploy:
- pushd build/ios/iphoneos
- mkdir Payload
- cd Payload
- ln -s ../Runner.app
- cd ..
- zip -r app.ipa Payload
- popd

You’ll need to have the Travis command-line tools installed: (if the command below isn’t found, install RubyGems from https://rubygems.org/)

$ gem install travis # Makes sure to have the Travis CLI installed

Then, setup deploying to GitHub releases:

$ cd your_project
$ travis setup releases

When prompted for a file to upload, type build/ios/iphoneos/app.ipa.

Make sure your .travis.yml ends like this:

deploy:
provider: releases
api_key:
secure: #your api key will be here
file: build/ios/iphoneos/app.ipa
skip_cleanup: true #important or your built app would be deleted
on:
repo: #your repo will be here

Now, push the changes to your GitHub repo:

$ git add .travis.yml
$ git commit
$ git push

Now, your app will be built and it will be added to your GitHub releases. Wait a bit for Travis to notice that you’ve added .travis.yml; when it starts you can track the progress of your build. When it’s done, download your app.ipa from your project’s GitHub releases.

Note: If you don’t want to publish your .ipa file you can add draft: true to deploy:, and it will be published as a draft on GitHub instead.

Installing and running the app

To install the IPA file, you need to download Cydia Impactor from the link below.

If you are running Windows, you need to install iTunes first. (Make sure to install the non-Microsoft store version: under “Looking for other versions?” select Windows, then scroll up and download).

Edit: a previous version of this article suggested installing the drivers bypassing iTunes installation. Apparently, that doesn’t work. (thanks to Andreas Opferkuch for pointing it out)

Update: Cydia Impactor seems to be broken at the moment. If you have a developer account, you may follow Weisser Zwerg’s tutorial (thanks for sharing!) to sign and install the app. If you have a jailbroken device, you may use AppSync unified and ideviceinstaller. There are alternative tools to Cydia Impactor, but I can’t guarantee they’re malware-free, so use them at your own risk.

  • Plug in your iOS device to your computer
  • Run the Impactor executable
  • Click “Xcode”, then “Revoke Certificates
  • Now, drag app.ipa (or the ZIP file you created) to the Cydia Impactor window (or use Device > Install Package…)
  • You will be prompted to enter your Apple ID email and password. If it’s protected with two-factor authentication or you get an error message, you will need to create an app-specific password and use it instead of your password. See https://support.apple.com/en-us/HT204397. (thanks to Near Cz for pointing it out!)
  • On your iOS device, go to Settings > General > Device Management (or Profiles & Device Management), tap your Apple ID email you just used to sign in, then Trust.
  • Hooray! Our app is now installed.

Preparing your machine

Install dependencies

Make sure libimobiledevice and ideviceinstaller are installed (the latter isn’t used but Flutter will complain if it isn’t present):

On Windows:

The previous link didn’t have binaries anymore, thanks to Andreas Opferkuch for sharing an alternative one!

Download imobiledevice binaries from here. You also need a “which” binary, which you can download here. Add these binaries to flutter\bin (or any other location, but make sure it’s in your PATH ).

On Arch/Manjaro/Antergos:

$ pacaur -S libimobiledevice ideviceinstaller-git

On Ubuntu/Mint/etc:

$ sudo apt install libimobiledevice6 ideviceinstaller

You might also need to install these packages (thanks João Matheus):

$ sudo apt install libimobiledevice-utils libusbmuxd-tools

Modify Flutter

When detecting devices, Flutter checks whether you’re running macOS before searching for iOS devices. It also checks for XCode. Let’s remove those checks.

Note: it’s not working with the current stable (1.17.0) version. In the meantime, you can use Flutter 1.12.13+hotfix.9 instead.

Apparently, now Flutter uses XCode utilities instead of libimobiledevice, so the patch will not be as straight-forward. In the meantime, use an older version, or help contribute with the patch or another solution (fork, or a way of undoing the specific commits with the changes). I’ll update the article once this gets solved.

We’ll apply these changes to Flutter

Download this file to your flutter folder (Click on “View raw” and press Ctrl+S). Then apply it: (this was tested with the stable branch on late April to early May)

$ cd flutter
$ git apply ios.diff

Then rebuild the Flutter tool:

Linux:

$ bin/cache/dart-sdk/bin/dart --snapshot=./bin/cache/flutter_tools.snapshot --packages=./packages/flutter_tools/.packages ./packages/flutter_tools/bin/flutter_tools.dart

Windows:

bin\cache\dart-sdk\bin\dart --snapshot=.\bin\cache\flutter_tools.snapshot --packages=.\packages\flutter_tools\.packages .\packages\flutter_tools\bin\flutter_tools.dart

Preparing your project(s)

Make sure there’s a folder inside your project called build. If it’s not present, create it!

Running and debugging

Here comes the good part!

Using an IDE (VS Code):

  • Open the command palette (Ctrl+Shift+P), and select “Debug: Attach to Flutter process”.
  • Open the app, and wait for Flutter to sync files.
  • You can now hot reload and hot restart as usual!

Tip: If hot reload doesn’t work, hot restart first!

Using the command-line:

  • Run flutter attach. This will wait until the app is launched and start debugging as if we’d used flutter run.
  • Open the app, and wait for Flutter to sync files.
  • You can now hot reload by pressing r and hot restart by pressing R!

Credits

Thanks to Yegor Jbanov for their post:

Have any questions or feedback? Please leave a comment!

If you liked this, don’t forget to clap, it’ll be greatly appreciated!

--

--