Mobile
Bellui apps run on Android and iOS from the same code as on the desktop. You
build them with the bellui command, which produces an
APK or an iOS app bundle. Rendering uses WebGPU: Vulkan on Android and Metal
on iOS. The OpenGL ES renderer is available with
--tags gl.
Trying it without a phone
The mobile build tag runs your app on your computer with the mobile driver,
the same code that runs on a phone:
go run -tags mobile .It behaves like a phone in the ways that matter for layout and flow:
CurrentDevice().IsMobile() is true, windows stack instead of opening side
by side, and dialogs stay inside the window. It does not emulate the operating
system, so test on a device or emulator before you ship.
How mobile apps behave
- Windows form a stack. Only the top visible window is shown. Opening a window covers the current one, and closing it reveals the one below.
- The OS runs the app’s lifecycle. On a phone
App.Quitcloses the app’s windows but does not stop the app; the system decides when the app stops. - Dialogs stay inside the app window.
SetWindowHostedhas no effect. - File dialogs use the system document pickers, which are the only way to reach files outside the app’s own storage. See File and folder dialogs.
Device, orientation and lifecycle
describe := func() {
d := bellui.CurrentDevice()
shape := "portrait"
if bellui.IsHorizontal(d.Orientation()) {
shape = "landscape"
}
info.SetText("Mobile: " + strconv.FormatBool(d.IsMobile()) +
"\nBrowser: " + strconv.FormatBool(d.IsBrowser()) +
"\nHardware keyboard: " + strconv.FormatBool(d.HasKeyboard()) +
"\nOrientation: " + shape)
}bellui.CurrentDevice
reports the orientation and whether a hardware keyboard is available.
bellui.IsVertical and bellui.IsHorizontal sort the four
DeviceOrientation
values into portrait and landscape. A layout that should change with the
orientation usually does not need this: the window is resized when the device
rotates, and layouts that respond to size, such as those described under
Layout, adapt by themselves.
App.Lifecycle
tells you when the app starts and stops, and when it goes to the background
and comes back. On a phone, going to the background is the moment to save
state, because the system may stop the app afterwards without warning.
life := a.Lifecycle()
life.SetOnStarted(describe)
life.SetOnEnteredForeground(func() {
describe() // the device may have been rotated while we were away
events.SetText("Back in the foreground")
})
life.SetOnExitedForeground(func() {
events.SetText("Went to the background")
})The on-screen keyboard
The on-screen keyboard appears when a widget that accepts typing gets focus,
and hides when focus leaves it. To control it directly, assert the device to
mobile.Device:
hide := widget.NewButton("Hide keyboard", func() {
if d, ok := bellui.CurrentDevice().(mobile.Device); ok {
d.HideVirtualKeyboard()
}
})ShowVirtualKeyboardType picks a layout: mobile.DefaultKeyboard,
SingleLineKeyboard, NumberKeyboard or PasswordKeyboard. A custom text
widget can choose its layout by implementing
mobile.Keyboardable.
Android
What you need:
- Go and a C compiler, as for any Bellui app;
- the Android SDK, with
ANDROID_HOMEpointing to it; - the Android NDK, found through
ANDROID_NDK_HOMEor in$ANDROID_HOME/ndk-bundle; - the SDK build tools, whose
apksignermakes the APK installable on Android 11 and later.
bellui package -os android --app-id com.example.notes
bellui install -os android --app-id com.example.notes # with adb, to a connected device-os android builds for all four architectures (arm, arm64, 386, amd64);
android/arm64 and the other variants build one. The app ID must contain a
dot, and no part of it may contain - or start with a digit or _. For an
adaptive launcher icon, set adaptive_icon in the
manifest. For Google Play, use
bellui release, which builds a signed .aab.
iOS
What you need:
- a Mac with Xcode;
- for a device, an Apple development certificate in your keychain.
# The simulator needs the iossimulator target and build tag.
bellui package -os iossimulator --tags iossimulator --app-id com.example.notes
bellui install -os iossimulator --tags iossimulator --app-id com.example.notes
# A device build.
bellui package -os ios --app-id com.example.notes
bellui install -os ios --app-id com.example.notes # needs ios-deployFor a device build, --certificate names the signing certificate and
--profile a provisioning profile. Leave --profile out to let Xcode sign
automatically for the certificate’s team. bellui install -os iossimulator
installs into the booted simulator and starts the app.
If your app asks for a permission, such as the camera, iOS requires a
sentence explaining why. Add it under ios_usage_descriptions in the
manifest. For the App Store, use
bellui release, which builds a signed .ipa.
The complete example
// Device information, orientation and app lifecycle on mobile.
package main
import (
"strconv"
"github.com/belluikit/bellui"
"github.com/belluikit/bellui/app"
"github.com/belluikit/bellui/container"
"github.com/belluikit/bellui/driver/mobile"
"github.com/belluikit/bellui/widget"
)
func main() {
a := app.NewWithID("com.example.mobile")
w := a.NewWindow("Device")
info := widget.NewLabel("")
events := widget.NewLabel("")
describe := func() {
d := bellui.CurrentDevice()
shape := "portrait"
if bellui.IsHorizontal(d.Orientation()) {
shape = "landscape"
}
info.SetText("Mobile: " + strconv.FormatBool(d.IsMobile()) +
"\nBrowser: " + strconv.FormatBool(d.IsBrowser()) +
"\nHardware keyboard: " + strconv.FormatBool(d.HasKeyboard()) +
"\nOrientation: " + shape)
}
life := a.Lifecycle()
life.SetOnStarted(describe)
life.SetOnEnteredForeground(func() {
describe() // the device may have been rotated while we were away
events.SetText("Back in the foreground")
})
life.SetOnExitedForeground(func() {
events.SetText("Went to the background")
})
hide := widget.NewButton("Hide keyboard", func() {
if d, ok := bellui.CurrentDevice().(mobile.Device); ok {
d.HideVirtualKeyboard()
}
})
w.SetContent(container.NewVBox(info, events, widget.NewEntry(), hide))
bellui.ShowAndRun(w)
}