Guides
Development
Ryui dev in depth, devices, tests and common problems.
ryui dev runs an app on the iOS simulator or an Android emulator or
phone, and reloads it as you save. This page covers how it does that,
what a reload keeps, devices, assets and logs, what it can't reload, the
problems you may meet, and RyUI's own GUI tests and benchmarks.
ryui dev
ryui dev -ios [flags] [dir]
ryui dev -android [flags] [dir]dir is the app's directory, the current one by default: a main
package that calls app.Run. The flags:
-ios,-android: the platform. One per run; run two terminals for both.-device: on iOS, a simulator's name or UDID (by default the booted iPhone, else a recent iPhone Pro, which it boots); on Android, an adb serial or an AVD's name (by default the first device adb lists, else the first AVD, which it starts).-embed: build the app with its Go code in it, and watch.-once: build the app with its Go code, install it and launch it, then exit: no watching, no logs.-v: print the commands RyUI runs.
The player and built apps
By default, ryui dev runs your Go code on your Mac, and its views in
the player: an app named RyUI (dev.ryui.player) that is the
platform's host with no app in it. The player is built and installed
once, shared by every app, and installed again only when it changes (a
new version of RyUI). The Go code and the player talk over a socket.
On a save, RyUI rebuilds the Go code for the Mac and restarts it, in about a second; the player reconnects and shows the new code. Nothing is installed.
With -embed or -once, or when the app has native code for the
platform (an ios/ or android/ directory, see Native
code), RyUI builds the app itself instead, its Go code inside
as a release build has it, under the app's own name and ID:
- iOS: the host, with the app's Swift, is compiled once and cached;
each save rebuilds the Go code, links the app, signs it for the
simulator and reinstalls it with
xcrun simctl. - Android: Gradle builds the app, with its Kotlin, once for each
version of the host, of the Kotlin and of the manifest; each save
rebuilds the Go library, pushes it into the app's private files with
adb, where the debug app loads it from, and restarts the app.
The player has every permission any RyUI package needs, and its own
purpose strings. What ryui.json gives the app itself (its name, its
ID, a URL scheme, links, ios.usage) only a built app has: try
those with -embed. The accent color applies in the player too.
What a reload keeps
In the player, a reload keeps the values of the app's signals, with no
code of yours. The Go process saves them as it stops, to the file the
RYUI_STATE environment variable names (ryui dev sets it to
state.json in the app's cache directory, under
~/Library/Caches/ryui), and the next run gives each value back to the
signal made at the same place: the same call path (function names, not
lines), the same type, and the same position among the signals of that
type made there.
What isn't kept:
- Navigation: a reload starts at the root screen.
- Signals RyUI makes itself, and values JSON doesn't carry (funcs, unexported fields).
- Signals whose type changed, and those of a type after one of the same type you added at the same place.
A built app (-embed, -once, native code) starts again from nothing
on each reload.
Files your Go code writes in the player (ryui/store, ryui/sqlite) are
on the Mac: store.Dir() is a data directory of the app's own, in
~/Library/Caches/ryui, one for each platform, as two devices would have
theirs. Delete it to start the app again with no data.
Reloading
RyUI watches, by polling, the app's directory and its module's:
- Go files and
go.mod(not_test.go); - Swift and Kotlin files;
- files under
assets/.
It skips directories starting with ., and testdata, build and
node_modules. A build that fails prints its errors and keeps the
running app; the next save tries again.
ryui.json isn't watched: ryui dev reads it as it starts. Stop it and
run it again after changing ryui.json; it rebuilds and reinstalls what
changed.
Native code reloads only in a built app, where RyUI rebuilds and reinstalls it on each save. Gradle runs again when Kotlin changes, which takes longer than a Go change.
Devices
The iOS simulator shares the Mac's network, so the player reaches
ryui dev directly.
An Android emulator reaches the Mac at 10.0.2.2, and the player
connects there.
An Android phone over USB: turn on USB debugging in its developer
options, plug it in and accept the prompt, and check that adb devices
lists it. ryui dev -android takes the first device adb lists, phone or
emulator; with several, name it with -device and its serial. The phone reaches the Mac through
adb reverse, which RyUI sets up for ports 7461 (the app) and 7462 (its
assets). In development RyUI builds for 64-bit phones and emulators
(arm64-v8a, x86_64).
An iPhone: ryui dev runs on the simulator only. To run on an
iPhone, make a build signed for it, ryui build -ios -distribution development (see Shipping), and install the .ipa
with Xcode's Devices window.
Assets
In the player, the app's assets/ directory stays on the Mac: the Go
process serves it over HTTP, and the player loads ui.Image's asset
paths, and the fonts in assets/fonts, from there. A change to an asset
reloads the app.
A built app has its assets inside it: on iOS, copied into the app on each build; on Android, pushed into the app's private files when they changed.
Logs
- In the player, the terminal shows the Go process's output:
fmt.Println,log, panics. - A built app on iOS: the app's output, Go's and Swift's
print, through the simulator's console. - A built app on Android: the app's logcat. Android drops a
process's standard output, so the Go code's goes to logcat, tagged
ryui. - With
-once, nothing: read them with Console.app oradb logcat.
Backgrounding the app
Leaving the app in the player (to take a photo with the camera app, to sign in in the browser) doesn't end the session. While the player is in the background, the Go code waits up to ten minutes for an answer from it, rather than the usual ten seconds, since the system may freeze it. When Android cuts a backgrounded app's network, the session is kept for three minutes; a player that comes back in time takes it up again, with its screens as they were. A player that comes back later starts the app again, at its root screen.
Stopping ryui dev (Ctrl-C) stops the player too, so it doesn't join
the next session, which may be another app's.
Common problems
no iOS simulator SDK (install Xcode): install Xcode, open it once, and add an iOS simulator runtime in its Components settings.no iPhone simulator; add one in Xcodeandno simulator "..."mean there's no simulator of that name:xcrun simctl list deviceslists them.no Android SDK: install Android Studio or set ANDROID_HOME: RyUI looks inANDROID_HOME,ANDROID_SDK_ROOT, then~/Library/Android/sdk.no Android NDK: install one from Android Studio's SDK Manager (SDK Tools, NDK).no Android device or emulator: make an AVD in Android Studio's Device Manager, or plug in a phone. RyUI waits up to four minutes for an emulator it starts to boot (the emulator didn't boot).- Java: Gradle needs a JDK:
JAVA_HOME, else Android Studio's own. - The first Android build is slow: Gradle downloads itself and Android's libraries, once.
- A release build on the phone: an app installed by
ryui build(another signing key) is uninstalled, with its data, to install the debug one. - The CLI and the app's RyUI: the CLI carries the hosts' Swift and
Kotlin, and the app's
go.modpicks RyUI's Go code. Install the CLI at the version the app requires. - Starting over: RyUI's builds and caches are in
~/Library/Caches/ryui; delete it to build everything again, and the kept state with it.
GUI tests
RyUI's own repository tests the examples on a simulator and an emulator,
through the platform's views, in e2e/:
go test -tags e2e ./e2e -run IOS # the booted simulator, else a recent iPhone
go test -tags e2e ./e2e -run Android # the device ryui dev picksEach test builds an example with ryui dev -once (a debug build, with
its Go code), then taps, types and reads the screen through the debug
hosts' automation: a notification on iOS, a broadcast on Android. The
counter, notes and navigation examples have tests, the same on both
platforms. They wait for the screen to show what they expect, up to a
minute, never for a set time.
The iOS tests need Xcode (xcrun); the Android ones need adb on your
PATH, and the SDK and NDK that ryui dev -android needs. Without
them, the tests are skipped.
Benchmarks
bench/ compares RyUI with SwiftUI and Jetpack Compose: the same app
written in each, timed frame by frame in release builds.
go run ./bench/run -ios # ryui and SwiftUI on the simulator
go run ./bench/run -android # ryui and Compose on the running emulator or device-runs sets how many runs of each (5 by default); the medians go to
bench/results. bench/README.md has the
scenarios, how they're measured, and the latest results.
ryui.json
ryui dev and ryui build read the same ryui.json; see
Shipping for its fields.