I have searched for a while for a good solution to this but there doesn't seem to be many others with the same setup as I have.
My setup consists of a low spec laptop (Surface pro 3) and a server machine (much higher spec) running ProxMox (which is where my Ubuntu 22.04 development VM resides).
What I am trying to achieve is to use VS Code remote development to develop the Flutter app on the server using remote development while viewing the output of the app on my Surface. My surface alone isn't powerful enough to run an Android emulator very well so it would be fantastic if the server could handle the grunt work and just forward the visual output to my Surface. Does anyone know how this would be possible?
The only success I have currently had is by running the Flutter app as a web server and then accessing the instance via Chrome with the Dart debugging extension. This isn't really ideal though. A part of me thinks that I may have to use a physical Android device and forward ADB to it or something but I'm unsure if this is the best option.
I'll be grateful for any help, thank you.
VS Code can indeed be used for remote development. I was able to achieve the described workflow using code-server and a bunch of SSH forwarding.
Here I describe how to set up code-server for Android development on Flutter with a remote machine used for build and debug tasks, and with a local machine used to access the editor GUI and to connect a physical device.
Verified on Ubuntu 22.04 (server), Fedora 36 (local), and Flutter 3.0.5.
code-server from the official repoImportant: After installing
code-servertells you to make systemd start your server automatically by running asystemctlcommand. Avoid doing that because that way ADB under VS Code won't detect devices. I haven't come up with any workaround for that yet to make it work under a systemd-managed instance.
PATHYour system might also need additional dependencies for the Flutter SDK to run. I recommend learning about it from the official manual. Prefer manual ways of installation described there.
After installation is done, update PATH variable in ~/.bashrc file to include /bin folder of the Flutter SDK, for example, add a line like this:
export PATH="$PATH:$HOME/path/to/flutter/bin"
after which, apply the changes:
source ~/.bashrc
I assume your server does not have any Desktop Environment, so we will install Android toolchain without Android Studio (since Studio requires a DE to run).
As of September 2023, Android Command line tools require minimal class file version 61.0 (Java 17).
You can check what version of Java you have installed by running java --version, you can also check where it is installed by running which java. If you already have Java installed, make sure it is compatible with cmdline-tools and your projects. You can install multiple Java versions and control your preferred version with sudo update-alternatives --config java command or JAVA_HOME environment variable.
Here we'll be installing OpenJDK from Ubuntu's repositories for simplicity. If you prefer so, you can install commercial Oracle Java from the official website.
To install OpenJDK 17, run:
sudo apt install openjdk-17-jdk
You can also install latest available OpenJDK version, to list available versions for install run:
sudo apt list openjdk-*-jdk
If you are trying to build an existing project
Older Flutter projects that did not target Android 14 might require older JDK version to build out of the box, likeopenjdk-11-jdk. Newer cmdline-tools won't work withopenjdk-11, so you will either have to use oldercmdline-toolsand JDK, or migrate the project to newer version. Google doesn't provide links to older releases ofcmdline-tools, but you can find them on the Internet Archive, for example, this archived copy of v9.
Go to Android Studio website and download "Command line tools only".
To download the archive directly onto your server, click the link on the Android Studio website, scroll down and agree to the terms, but instead of clicking the "Download Android Command Line Tools for Linux" button, right click it and copy the URL. Then download it from your server, for example, using wget:
wget https://dl.google.com/android/repository/commandlinetools-linux-XXX_latest.zip
Unpack them with unzip commandlinetools-linux-XXX_latest.zip command in a desired location. Calling unzip will create a cmdline-tools folder in your current location. I recommend creating this folder structure when unpacking the archive:
~/path/to/android-sdk/cmdline-tools
This way, when sdkmanager downloads its packages, new folders will be created inside the android-sdk folder.
As of September 2023, sdkmanager inside Android command line tools requires a special folder hierarchy, which can be achieved by putting content of the cmdline-tools folder inside a latest folder under it with this command:
mv ./cmdline-tools/ ./latest && mkdir cmdline-tools && mv ./latest/ ./cmdline-tools/latest/
Add the tools to your PATH in ~/.bashrc file and specify ANDROID_SDK_ROOT by adding new lines:
export ANDROID_SDK_ROOT="$HOME/path/to/android-sdk"
export PATH="$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools"
Don't forget to run source ~/.bashrc
Flutter SDK requires three packages to be installed: build-tools, platforms and platform-tools.
sdkmanager "build-tools;34.0.0" "platforms;android-34" "platform-tools"
34.0.0 is the latest version of build-tools, and android-34 is the latest version of platforms, as of September 2023, targeting Android 14.
Learn what the latest version of build-tools is available by running:
sdkmanager --list | grep build-tools
And for platforms:
sdkmanager --list | grep "platforms;android"
For existing projects, Flutter will automatically download required SDKs to build them.
Run sdkmanager --licenses and accept all licenses by pressing the y key
After installing code-server, you can now access the editor from your browser.
It is also recommended to install it as a PWA, so you will have more screen space, more options for keyboard shortcuts and ability to launch the editor from the system launcher.
{
"dart.flutterRunAdditionalArgs": [
// Dart Developer Service port (debugger)
"--dds-port=10388",
// Dart VM Service instance port (device)
"--host-vmservice-port=10389"
],
"dart.devToolsPort": 9100,
}
By default, Dart chooses random ports for connection between the debugger and the device. By setting these settings we make the ports static, so we can forward them easily.
You might want to add dart.devToolsLocation: external to your config if your browser or adblocker prevents "local network intrusion", aka preventing websites from accessing localhost ports. Though, I'd recommend adding your code-server instance into exceptions instead.
For debugging an Android app with Flutter, we'll have to forward 4 ports. The table lists the port numbers according to the VS Code settings above. You can use your own port numbers, but then you must change the configs accordingly.
| Port | Description | Forwarding |
|---|---|---|
| 5037 | ADB | To remote |
| 10388 | Dart Developer Service | To local |
| 10389 | VM Service on device | To remote |
| 9100 | Dart DevTools | To local |
SSH can be used to forward ports.
To forward a port from localhost to remote host, run on the local machine:
ssh -R XXXX:localhost:XXXX user@host
To forward a port from remote host to localhost, run on the local machine:
ssh -L XXXX:localhost:XXXX user@host
You can chain options to ssh command, for example:
ssh -R 5037:localhost:5037 -L 10388:localhost:10388 -R 10389:localhost:10389 -L 9100:localhost:9100 user@host
Port forwarding will be active until you close the SSH connection.
Make sure your firewall is set up to allow port forwarding.
I made a script that automates possible quirks around the process:
code-server, node, and adb.
You will have to customize this if this doesn't work for you.VSCODE_PROXY_URI to be empty to turn off port forwarding URL overrides, which break Dart DevTools. Starts ADB and launches code-server.It is intended to be run on the local machine.
#!/bin/bash
# Remote machine
CE_MACHINE="user@host"
# Local machine, no need to change
CE_LOCALHOST="localhost"
# Default port for ADB daemon is 5037
CE_ADB_PORT="5037"
# You might need to specify exact path to adb on your
# remote system since .bashrc is not sourced for
# non-interactive sessions (see https://stackoverflow.com/a/6212684)
CE_ADB_EXECUTABLE="~/dev/tools/android-sdk/platform-tools/adb"
# "Dart Developer Service port
CE_DDS_PORT="10388"
# Dart VM Service instance port
CE_HOST_VMSERVICE_PORT="10389"
# Flutter DevTools port
CE_DEVTOOLS_PORT="9100"
#### VSCode Settings ####
# "dart.flutterRunAdditionalArgs": [
# "--dds-port=10388",
# "--host-vmservice-port=10389",
# ],
# "dart.devToolsPort": 9100
#### VSCode Settings ####
# Reset ADB on local machine
# so it releases used ports
killall adb
adb devices
## INSERT ADDITIONAL LINES HERE
# When `adb devices` is called, ADB checks the daemon port.
# If it detects there's no response on the port, it
# launches a new daemon.
#
# After killing ADB and forwarding the ADB port to local machine,
# a newly launched ADB client will bind to the existing connection
# (which is our physical device) instead of launching a daemon on
# the remote machine.
#
# Restart code-server
# ADB doesn't detect devices if code-server is managed by systemctl
# WARNING, killing all codee-server, Node, Dart, and ADB processes here. Customize if needed.
#
# 1. ADB forwarding Local -> Remote
# 2. Dart Dev Server forwarding Remote -> Local
# 3. Dart on-device debugger client forwarding Local -> Remote
# 4. Flutter DevTools Remote -> Local forwarding
ssh -R $CE_ADB_PORT:$CE_LOCALHOST:$CE_ADB_PORT \
-L $CE_DDS_PORT:$CE_LOCALHOST:$CE_DDS_PORT \
-R $CE_HOST_VMSERVICE_PORT:$CE_LOCALHOST:$CE_HOST_VMSERVICE_PORT \
-L $CE_DEVTOOLS_PORT:$CE_LOCALHOST:$CE_DEVTOOLS_PORT \
$CE_MACHINE "killall code-server; killall node; killall dart; killall adb; $CE_ADB_EXECUTABLE devices; VSCODE_PROXY_URI= code-server"
Sometimes connection can be broken unexpectedly, so ports will be busy and forwarding won't work. You can add these lines after the adb devices line to the script above:
payload() {
cat <<EOF
for port in $CE_ADB_PORT $CE_DDS_PORT $CE_HOST_VMSERVICE_PORT $CE_DEVTOOLS_PORT; do
pid="\$(sudo ss -tulpn | grep ":\$port" | grep -Po 'pid=\\d+,' | grep -Po '\\d+' | uniq)"
if [ ! -z \$pid ]; then
kill "\$pid"
echo Freed :\$port from PID \$pid
fi
done
EOF
}
if [ ! -z "$1" ]; then
payload | ssh $CE_MACHINE /bin/bash
fi
With the code added, if you pass any argument to the script, it will attempt to free the used ports on the remote machine (e.g. script.sh kill).
This code enumerates processes that keep the used ports busy, and then kills them.
For sudo ss to work in the script for non-superuser, you'll need to add the following line to /etc/sudoers by opening the file with sudo visudo command. Doing this will allow your user run sudo ss without entering password:
myusername ALL=(root) NOPASSWD: /usr/bin/ss
Alternatively, you can store your password in the script and change the script line the following way:
echo YOURPASSWORD | sudo -S
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With