This directory contains the Lexe mobile app UI, which is written in Dart+Flutter.
After following these setup steps, you'll be able to test and run the Lexe app, on device or simulator, for both Android and iOS.
We'll also default to installing our tooling in ~/.local
, so that everything
is accessible in one place.
We'll install Java and the Android SDKs via CLI, as it's more repeatable.
Add this to your home-manager config somewhere and then home-manager switch
:
programs.bash.initExtra = ''
export JAVA_HOME=${pkgs.jdk17_headless.home}
'';
We only export it via JAVA_HOME
to avoid polluting our $PATH
with Java gunk.
Sanity check:
$ $JAVA_HOME/bin/java --version
openjdk 17.0.10 2024-01-16 LTS
OpenJDK Runtime Environment Zulu17.48+15-CA (build 17.0.10+7-LTS)
OpenJDK 64-Bit Server VM Zulu17.48+15-CA (build 17.0.10+7-LTS, mixed mode, sharing)
If you don't have home-manager
, you could use sdkman
, which is like rustup
but for Java.
Download the sdkman
install script:
$ cd ~/.local
$ curl --proto '=https' --tlsv1.3 -sSf "https://get.sdkman.io?rcupdate=false" > sdkman-install.sh
$ sha256sum sdkman-install.sh
419762944a301418a6c68021c5c864f54a3ce3e013571bd38da448439695f582
# If missing the sha256sum command on macOS:
$ brew install coreutils
Install sdkman
:
$ export SDKMAN_DIR="$HOME/.local/sdkman"
$ chmod a+x ./sdkman-install.sh
$ ./sdkman-install.sh
Run the init script:
$ source ~/.local/sdkman/bin/sdkman-init.sh
Ensure the init script is always run at startup by adding the following lines to
your .bashrc
or equivalent:
export SDKMAN_DIR="$HOME/.local/sdkman" # Default is ~/.sdkman
if [[ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]]; then
source "$SDKMAN_DIR/bin/sdkman-init.sh"
fi
Clean up
$ rm sdkman-install.sh
Install the JDK. Unfortunately, the Java ecosystem is a bit more... convoluted than the Rust ecosystem, so we have to choose a JDK "distribution" to install.
As of 2024-06-19, Android compileSdk
v34 requires JDK v17.
See: https://developer.android.com/build/jdks#compileSdk.
$ sdk list java
$ sdk install java 17.0.10-zulu
# Sanity check
$ which javac
~/.local/sdkman/candidates/java/current/bin/javac
$ javac --version
javac 17.0.10
This step will give us the Android sdkmanager
from the cmdline-tools
"package", which we'll use to actually install the Android SDKs.
You can find the latest cmdline-tools
download links here:
https://developer.android.com/studio#command-line-tools-only
$ cd ~/.local/
# (Linux only) download
$ wget https://dl.google.com/android/repository/commandlinetools-linux-9123335_latest.zip -O commandlinetools.zip
$ sha256sum commandlinetools.zip
0bebf59339eaa534f4217f8aa0972d14dc49e7207be225511073c661ae01da0a
# (macOS only) download
$ brew install wget (if needed)
$ wget https://dl.google.com/android/repository/commandlinetools-mac-9123335_latest.zip -O commandlinetools.zip
$ sha256sum commandlinetools.zip
d0192807f7e1cd4a001d13bb1e5904fc287b691211648877258aa44d1fa88275
# see zip file structure
$ unzip -l commandlinetools.zip
cmdline-tools/bin/...
cmdline-tools/lib/...
cmdline-tools/...
# install into ~/.local/android/cmdline-tools/latest
$ mkdir -p android/cmdline-tools/latest
$ unzip commandlinetools.zip -d android/cmdline-tools/latest
$ mv android/cmdline-tools/latest/cmdline-tools/* android/cmdline-tools/latest/
$ rmdir android/cmdline-tools/latest/cmdline-tools
$ rm commandlinetools.zip
Ensure .bashrc
contains these lines:
# Most android tools rely on $ANDROID_HOME to find the SDK
export ANDROID_HOME=$HOME/.local/android
# sdkmanager, avdmanager, ...
ANDROID_PATH=$ANDROID_HOME/cmdline-tools/latest/bin
# adb, fastboot, ...
ANDROID_PATH=$ANDROID_PATH:$ANDROID_HOME/platform-tools
if [[ ! "$PATH" == *$ANDROID_PATH* ]]; then
export PATH="$PATH:$ANDROID_PATH"
fi
Finally check our install:
# reload $PATH
$ source ~/.bashrc
# sanity check
$ which sdkmanager
~/.local/android/cmdline-tools/latest/bin/sdkmanager
$ sdkmanager --version
8.0
Let's blindly accept every license : )
$ yes | sdkmanager --licenses
Check out the available SDK packages
$ sdkmanager --list
add-ons;addon-google_apis-google-24
build-tools;33.0.1
cmake;3.22.1
cmdline-tools;latest
emulator
extras;android;m2repository
extras;google;auto
extras;google;google_play_services
# .. this goes on for a while
Install these. You may need to update the version #'s. They should generally
match the values in app/android/app/build.gradle
and app_rs_dart/android/build.gradle
.
$ sdkmanager --install \
"build-tools;34.0.0" \
"ndk;26.3.11579264" \
"platform-tools" \
"platforms;android-34" \
"sources;android-34"
Sanity check
$ adb version
Android Debug Bridge version 1.0.41
Version 35.0.1-11580240
Installed as /home/phlip9/.local/android/platform-tools/adb
Running on Linux 6.8.0-76060800daily20240311-generic (x86_64)
$ sdkmanager --update
You may also have to manually update some tools by selecting the newer versions
from sdkmanager --list --newer
.
If you only plan to use Wireless debugging, you can skip this section. Otherwise you'll need to jump through a few extra hoops on linux machines:
Add your user to the plugdev
group:
$ sudo usermod -aG plugdev $LOGNAME
Install udev
rules:
$ sudo apt install android-sdk-platform-tools-common
Restart your machine. adb devices
should now pick up any connected devices.
$ sudo softwareupdate --install-rosetta --agree-to-license
Either download the app directly from https://developer.apple.com/xcode/download/ or install it from the Mac App Store.
Once installed, run
$ sudo xcode-select \
--switch /Applications/Xcode.app/Contents/Developer
$ sudo xcodebuild -runFirstLaunch
From the "Sudo-less Install" section: https://guides.cocoapods.org/using/getting-started.html#installation
Ensure your .bashrc
contains something like:
export GEM_HOME=$HOME/.local/gem
GEM_BIN=$GEM_HOME/bin
if [[ ! "$PATH" == *$GEM_BIN* ]]; then
export PATH="$PATH:$GEM_BIN"
fi
Re-source .bashrc
, check that gem
is installed
$ source ~/.bashrc
$ echo $GEM_HOME
~/.local/gem
$ gem --version
3.0.3.1
Install the cocoapods
gem. This command also updates cocoapods
if already
intalled.
$ gem install cocoapods
Sanity check
$ pod --version
1.15.0
Download the latest iOS platform (~7.5 GiB)
$ xcodebuild -downloadPlatform iOS
Search for "Simulator" in Spotlight and then open it. An emulated iPhone should pop up after a minute or so.
Flutter should then pick up the simulated iPhone as an available target:
$ just flutter devices
Found 3 connected devices:
iPhone 15 Pro Max (mobile) • D8810737-2E02-4EF5-83DA-72934A34398B • ios •
com.apple.CoreSimulator.SimRuntime.iOS-17-0 (simulator)
...
If this doesn't work (no iPhone shows up), try running a random sample iOS app in Xcode -- this seems to force Xcode to actually install everything.
A quick way to do this is to create a new project based on the "App" template. Set the "Product Name" to whatever, set the "Organization Identifier" to whatever, then run the app by pressing the "Play" button near the top of the screen. The app should build and the simulated iPhone should pop up on the screen. The temporary project can then be deleted from wherever it was created (defaults to Desktop).
If you want to run flutter linux desktop apps:
$ sudo apt install libstdc++-12-dev
From the setup instructions online: https://docs.flutter.dev/get-started/install
Ensure your .bashrc
contains something like
export FLUTTER_HOME=$HOME/.local/flutter/bin
if [[ ! "$PATH" == *$FLUTTER_HOME* ]]; then
export PATH="$PATH:$FLUTTER_HOME"
fi
Re-source .bashrc
$ source ~/.bashrc
$ echo $FLUTTER_HOME
~/.local/flutter/bin
Installing flutter
means just cloning their repo. Make sure you don't do a
shallow clone (--depth=1
); that breaks the flutter upgrades.
$ git clone \
--branch="stable" \
https://github.com/flutter/flutter.git \
~/.local/flutter
Disable their pesky telemetry : )
$ flutter config --suppress-analytics --no-analytics
$ dart --disable-analytics
Run flutter doctor
to check your install:
$ flutter doctor
Downloading Material fonts... 1,031ms
Downloading Gradle Wrapper... 201ms
...
[✓] Flutter (Channel beta, 3.7.0-1.5.pre, on Pop!_OS 22.04 LTS 6.0.6-76060006-generic, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
[✗] Chrome - develop for the web (Cannot find Chrome executable at google-chrome)
! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.
[✓] Linux toolchain - develop for Linux desktop
[!] Android Studio (not installed)
[✓] Connected device (1 available)
[✓] HTTP Host Availability
! Doctor found issues in 2 categories.
This output is from my Pop!_OS linux desktop. Since we don't care about flutter web (it's trash) and Android Studio is not actually necessary (I don't use it), we're all good to go!
Now let's check that flutter picks up any connected devices or simulators. If you have an actual Android or iOS device, make sure they have Debugging/Dev mode turned on and are connected to your machine.
(On Android phone) To turn on debugging / dev mode, go to Settings > About phone > Software information, then tap the Build number pane 7 times. Then, ensure that the "USB debugging" option is turned on under Settings > Developer options.
On my Pop!_OS linux desktop:
$ flutter devices
2 connected devices:
Pixel 5a (mobile) • android-arm64 • Android 13 (API 33)
Linux (desktop) • linux-x64 • Pop!_OS 22.04 LTS
On my M1 MBP:
$ flutter devices
3 connected devices:
Pixel 5a (mobile) • android-arm64 • Android 13 (API 33)
iPhone 14 Pro (mobile) • ios • iOS-16-2 (simulator)
macOS (desktop) • macos • darwin-arm64 • macOS 13.1 darwin-arm
SM N975U1 (mobile) • RF8M80RGR3J • android-arm64 • Android 12 (API 31)
$ mkdir -p ~/flutter-test
$ cd ~/flutter-test
$ flutter create --platforms ios,android,windows,linux,macos my_app
$ cd my_app
# this should build, install, and launch the sample app on your
# phone/simulator/desktop. Trying each takes a few minutes.
$ flutter run -d pixel
$ flutter run -d "SM N975U1" # Samsung; copy the name from `flutter devices`
$ flutter run -d iphone
$ flutter run -d mac
$ flutter run -d linux
# Clean up
$ cd ~
$ rm -rf ~/flutter-test
$ cd app/
$ flutter pub get
flutter run
will also automatically run flutter pub get
whenever the
dependencies change in pubspec.yaml
.
For all the nvim
chads out there using coc.nvim
, just do:
:CocInstall coc-flutter
Set "dart.showTodos": false
in your coc-settings.json
.
To run the Lexe app in debug mode, run the following in the app/
directory.
$ flutter run
While running in debug mode, you can hit r
in the terminal window to hot
reload the UI. If you make changes any StatefulWidget
s or other logic before
runApp(..)
, you'll need to hot restart with R
.
When evaluating UI performance, run the app in profiling mode. This enables just enough debug info to be useful without slowing everything down like debug mode.
$ flutter run --profile
Run unit tests:
$ flutter test
Run integration tests on device or emulator:
$ flutter test integration_test
The Lexe App implements some logic as native Rust code. This allows us to share code between our backend services, lightning nodes, and mobile apps.
The native code is made available to the Flutter app via
flutter_rust_bridge
in
app_rs_dart/lib/ffi/ffi.dart
app-rs
is the Rust crate which contains the shared
interface and code.
After making changes to app-rs/src/ffi/ffi.rs
,
be sure to regenerate the Dart+Rust FFI bindings:
$ cargo run -p app-rs-codegen
The current build process looks like this:
-
A dev or CI instance runs
just flutter run
orjust flutter build
, which builds the top-levelapp
application. -
Eventually, flutter will build the
app_rs_dart
dart package, which contains the dart-side ffi bindings and build system integrations. The top-levelapp
package depends on this package. -
While building
app_rs_dart
,flutter
delegates togradle
(Android),Xcode/CocoaPods
(iOS/macOS),cmake
(Linux), or TODO (Windows) to build the native shared library. -
Hooks are added so that during the platform build tool's build process, it unconditionally invokes
cargo build
on theapp-rs
crate. We don't mind always runningcargo build
sincecargo
has good incremental compilation support and does nothing when no inputs have changed.-
(Android) we hook
gradle
inandroid/app/build.gradle
, so it callsandroid/build_rust.sh
. This script usescargo-ndk
under-the-hood to wrap thecargo
invocation. -
(iOS/macOS) the cocoapods package definitions in
app_rs_dart/{ios,macos}/app_rs_dart.podspec
contain ascript_phase
hook, whichxcodebuild
interprets during the build and calls theapp_rs_dart/build_ios_macos.sh
script. This script shells out tocargo build -p app-rs
to build a static library that gets linked into the finalapp_rs_dart
shared library. -
(Linux) TODO
-
(Windows) TODO
-
-
Flutter finishes building the
app_rs_dart
package and produces a shared library, likelibapp_rs_dart.so
on Android. -
The flutter build for the top-level
app
application package then bundles the shared library with the final application, so we can load it at runtime.
- Unfortunately, the hot-reload and hot-restart features for
flutter run
don't support reloading native libraries. If you've changedapp-rs
and want to see the effects, you'll need to full-restartflutter run
.