8

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.

Weagertron
  • 161
  • 2
  • 12

1 Answers1

11

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.

Install software and SDKs on remote machine

1. Install code-server from the official repo

Important: After installing code-server tells you to make systemd start your server automatically by running a systemctl command. 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.

2. Install Flutter SDK and update your PATH

Your 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 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

3. Install Android toolchain

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).

Download cmdline-tools

Go to Android Studio website and download "Command line tools only". Unpack them with unzip command in a desired 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 August 2022, 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 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

Install SDK

Flutter SDK requires two packages to be installed: build-tools and platform-tools.

sdkmanager "build-tools;33.0.0" "platform-tools"

33.0.0 is the latest version of build-tools as of August 2022. Learn what the latest version of build-tools is available by running:

sdkmanager --list | grep build-tools

Accept licenses

Run sdkmanager --licenses and accept all licenses by pressing the y key

VS Code setup

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.

  1. Install the Flutter extension
  2. Add these settings to VS Code user settings:
{
    "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.

Port forwarding with SSH on local machine

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

Commands

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.

Automation script

I made a script that automates possible quirks around the process:

  1. Kills local ADB and restarts it to release used ports
  2. Sets up port forwarding for specified ports to a remote host. The script expects you to use keys for SSH authentication.
  3. Kills all running instances of code-server, node and adb. You will have to customize this if this doesn't work for you.
  4. Starts ADB and launches code-server.

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

# 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; code-server"

Optional

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="\$(ss -tulpn | grep ":\$port" | grep -Po 'pid=\\d+,' | grep -Po '\\d+' | uniq)"
    if [ ! -z \$pid ]; then
      kill "\$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.

Sominemo
  • 331
  • 2
  • 7
  • Even with port forwarding, I still get the following error, am I missing something else [CHROME]:[13746:13746:1222/081308.099592:ERROR:ozone_platform_x11.cc(238)] Missing X server or $DISPLAY [CHROME]:[13746:13746:1222/081308.099634:ERROR:env.cc(255)] The platform failed to initialize. Exiting. Failed to launch browser after 3 tries. Command used to launch it: google-chrome --user-data-dir ...... Failed to launch browser. Make sure you are using an up-to-date Chrome or Edge. Otherwise, consider using -d web-server instead and filing an issue at https://github.com/flutter/flutter/issues. – Krishna Dec 22 '22 at 02:44
  • and it keeps adding more ports to forward in vscode – Krishna Dec 22 '22 at 02:47
  • I use a different web app development platform that forwards the port and waits instead of terminating and shows me the URL, which I open in chrome browser and all works fine. Looks like Flutter is exiting after failing to launch browser – Krishna Dec 22 '22 at 02:48
  • When I pick the Webserver as the device instead of chrome, it is able to spawn web-browser, it might be a new feature, should investigate further – Krishna Dec 22 '22 at 10:46
  • `flutter run -d web-server` on cmd line worked for me – Krishna Dec 22 '22 at 14:33