wails/v3/ANDROID_ARCHITECTURE.md
Lea Anthony 4d8ec29feb feat: Add Android support for Wails v3
This commit adds comprehensive Android support for Wails v3, enabling
Go applications to run as native Android apps with WebView-based UI.

Key features:
- Android-specific application implementation with JNI bridge
- WebView integration via WebViewAssetLoader for serving assets
- JavaScript runtime injection and execution via JNI callbacks
- Binding call support with async result callbacks
- Event system support for Android platform
- Full example Android app with Gradle build system

Technical details:
- Uses CGO with Android NDK for cross-compilation
- Implements JNI callbacks for Go <-> Java communication
- Supports both ARM64 and x86_64 architectures
- WebView debugging support via Chrome DevTools Protocol
- Handles empty response body case in binding calls to prevent panic

Files added:
- v3/pkg/application/*_android.go - Android platform implementations
- v3/pkg/events/events_android.go - Android event definitions
- v3/internal/*/\*_android.go - Android-specific internal packages
- v3/examples/android/ - Complete example Android application
- v3/ANDROID_ARCHITECTURE.md - Architecture documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 21:06:59 +11:00

32 KiB

Wails v3 Android Architecture

Executive Summary

This document provides a comprehensive technical architecture for Android support in Wails v3. The implementation enables Go applications to run natively on Android with an Android WebView frontend, maintaining the Wails philosophy of using web technologies for UI while leveraging Go for business logic.

Unlike iOS which uses CGO with Objective-C, Android uses JNI (Java Native Interface) to bridge between Java/Kotlin and Go. The Go code is compiled as a shared library (.so) that is loaded by the Android application at runtime.

Table of Contents

  1. Architecture Overview
  2. Core Components
  3. Layer Architecture
  4. File Structure
  5. Implementation Details
  6. Build System
  7. JNI Bridge Details
  8. Asset Serving
  9. JavaScript Bridge
  10. Security Considerations
  11. Configuration Options
  12. Debugging
  13. API Reference
  14. Troubleshooting
  15. Future Enhancements

Architecture Overview

Design Principles

  1. Battery Efficiency First: All architectural decisions prioritize battery life
  2. No Network Ports: Asset serving happens in-process via WebViewAssetLoader
  3. JNI Bridge Pattern: Java Activity hosts WebView, Go provides business logic
  4. Wails v3 Compatibility: Maintain API compatibility with existing Wails v3 applications
  5. Follow Fyne's gomobile pattern: Use -buildmode=c-shared for native library

High-Level Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Android Application                       │
├─────────────────────────────────────────────────────────────┤
│                    Java/Android Layer                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              MainActivity (Activity)                 │   │
│  │  ┌───────────────────────────────────────────────┐  │   │
│  │  │            Android WebView                     │  │   │
│  │  │  ┌─────────────────────────────────────────┐  │  │   │
│  │  │  │         Web Application (HTML/JS)        │  │  │   │
│  │  │  └─────────────────────────────────────────┘  │  │   │
│  │  └───────────────────────────────────────────────┘  │   │
│  │                                                      │   │
│  │  WailsBridge        WailsPathHandler   WailsJSBridge│   │
│  └─────────────────────────────────────────────────────┘   │
├─────────────────────────────────────────────────────────────┤
│                  JNI Bridge Layer                            │
│            System.loadLibrary("wails")                       │
├─────────────────────────────────────────────────────────────┤
│                    Go Runtime (libwails.so)                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                 Wails Application                     │  │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │  │
│  │  │App Logic │  │Services  │  │Asset Server      │  │  │
│  │  └──────────┘  └──────────┘  └──────────────────┘  │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Comparison with iOS Architecture

Aspect iOS Android
Native Language Objective-C Java
Bridge Technology CGO (C headers) JNI
Build Mode -buildmode=c-archive (.a) -buildmode=c-shared (.so)
Entry Point main.m calls WailsIOSMain() MainActivity loads libwails.so
WebView WKWebView Android WebView
URL Scheme wails://localhost https://wails.localhost
Asset Interception WKURLSchemeHandler WebViewAssetLoader + PathHandler
JS → Native WKScriptMessageHandler @JavascriptInterface
Native → JS evaluateJavaScript: evaluateJavascript()
App Lifecycle UIApplicationDelegate Activity lifecycle methods

Core Components

1. Java Components

MainActivity (MainActivity.java)

Purpose: Android Activity that hosts the WebView and manages app lifecycle.

Location: build/android/app/src/main/java/com/wails/app/MainActivity.java

Key Responsibilities:

  • Initialize the native Go library via WailsBridge
  • Configure and manage the Android WebView
  • Set up asset loading via WebViewAssetLoader
  • Handle Android lifecycle events (onCreate, onResume, onPause, onDestroy)
  • Execute JavaScript in the WebView when requested by Go

Key Methods:

onCreate(Bundle)           // Initialize bridge, setup WebView
setupWebView()             // Configure WebView settings and handlers
loadApplication()          // Load initial URL (https://wails.localhost/)
executeJavaScript(String)  // Run JS code (called from Go via JNI)
onResume() / onPause()     // Lifecycle events forwarded to Go
onDestroy()                // Cleanup resources
onBackPressed()            // Handle back navigation

WailsBridge (WailsBridge.java)

Purpose: Manages the JNI connection between Java and Go.

Location: build/android/app/src/main/java/com/wails/app/WailsBridge.java

Key Responsibilities:

  • Load the native library (System.loadLibrary("wails"))
  • Declare and call native methods
  • Manage callbacks for async operations
  • Forward lifecycle events to Go

Native Method Declarations:

private static native void nativeInit(WailsBridge bridge);
private static native void nativeShutdown();
private static native void nativeOnResume();
private static native void nativeOnPause();
private static native byte[] nativeServeAsset(String path, String method, String headers);
private static native String nativeHandleMessage(String message);
private static native String nativeGetAssetMimeType(String path);

Key Methods:

initialize()              // Call nativeInit, set up Go runtime
shutdown()                // Call nativeShutdown, cleanup
serveAsset(path, method, headers)  // Get asset data from Go
handleMessage(message)    // Send message to Go, get response
getAssetMimeType(path)    // Get MIME type for asset
executeJavaScript(js)     // Execute JS (callable from Go)
emitEvent(name, data)     // Emit event to frontend

WailsPathHandler (WailsPathHandler.java)

Purpose: Implements WebViewAssetLoader.PathHandler to serve assets from Go.

Location: build/android/app/src/main/java/com/wails/app/WailsPathHandler.java

Key Responsibilities:

  • Intercept all requests to https://wails.localhost/*
  • Forward requests to Go's asset server via WailsBridge
  • Return WebResourceResponse with asset data

Key Method:

@Nullable
public WebResourceResponse handle(@NonNull String path) {
    // Normalize path (/ -> /index.html)
    // Call bridge.serveAsset(path, "GET", "{}")
    // Get MIME type via bridge.getAssetMimeType(path)
    // Return WebResourceResponse with data
}

WailsJSBridge (WailsJSBridge.java)

Purpose: JavaScript interface exposed to the WebView for Go communication.

Location: build/android/app/src/main/java/com/wails/app/WailsJSBridge.java

Key Responsibilities:

  • Expose methods to JavaScript via @JavascriptInterface
  • Forward messages from JavaScript to Go
  • Support both sync and async message patterns

JavaScript Interface Methods:

@JavascriptInterface
public String invoke(String message)  // Sync call to Go

@JavascriptInterface
public void invokeAsync(String callbackId, String message)  // Async call

@JavascriptInterface
public void log(String level, String message)  // Log to Android logcat

@JavascriptInterface
public String platform()  // Returns "android"

@JavascriptInterface
public boolean isDebug()  // Returns BuildConfig.DEBUG

Usage from JavaScript:

// Synchronous call
const result = wails.invoke(JSON.stringify({type: 'call', ...}));

// Asynchronous call
wails.invokeAsync('callback-123', JSON.stringify({type: 'call', ...}));

// Logging
wails.log('info', 'Hello from JavaScript');

// Platform detection
if (wails.platform() === 'android') { ... }

2. Go Components

Application Layer (application_android.go)

Purpose: Main Go implementation for Android platform.

Location: v3/pkg/application/application_android.go

Build Tag: //go:build android

Key Responsibilities:

  • Export JNI functions for Java to call
  • Manage global application state
  • Handle lifecycle events from Android
  • Serve assets and process messages

JNI Exports:

//export Java_com_wails_app_WailsBridge_nativeInit
func Java_com_wails_app_WailsBridge_nativeInit(env *C.JNIEnv, obj C.jobject, bridge C.jobject)

//export Java_com_wails_app_WailsBridge_nativeShutdown
func Java_com_wails_app_WailsBridge_nativeShutdown(env *C.JNIEnv, obj C.jobject)

//export Java_com_wails_app_WailsBridge_nativeOnResume
func Java_com_wails_app_WailsBridge_nativeOnResume(env *C.JNIEnv, obj C.jobject)

//export Java_com_wails_app_WailsBridge_nativeOnPause
func Java_com_wails_app_WailsBridge_nativeOnPause(env *C.JNIEnv, obj C.jobject)

//export Java_com_wails_app_WailsBridge_nativeServeAsset
func Java_com_wails_app_WailsBridge_nativeServeAsset(env *C.JNIEnv, obj C.jobject, path, method, headers *C.char) *C.char

//export Java_com_wails_app_WailsBridge_nativeHandleMessage
func Java_com_wails_app_WailsBridge_nativeHandleMessage(env *C.JNIEnv, obj C.jobject, message *C.char) *C.char

//export Java_com_wails_app_WailsBridge_nativeGetAssetMimeType
func Java_com_wails_app_WailsBridge_nativeGetAssetMimeType(env *C.JNIEnv, obj C.jobject, path *C.char) *C.char

Platform Functions:

func (a *App) platformRun()      // Block forever, Android manages lifecycle
func (a *App) platformQuit()     // Signal quit
func (a *App) isDarkMode() bool  // Query Android dark mode

WebView Window (webview_window_android.go)

Purpose: Implements webviewWindowImpl interface for Android.

Location: v3/pkg/application/webview_window_android.go

Build Tag: //go:build android

Key Methods: Most methods are no-ops or return defaults since Android has a single fullscreen window.

func (w *androidWebviewWindow) execJS(js string)     // Execute JavaScript
func (w *androidWebviewWindow) isFullscreen() bool   // Always true
func (w *androidWebviewWindow) size() (int, int)     // Device dimensions
func (w *androidWebviewWindow) setBackgroundColour(col RGBA)  // Set WebView bg

Asset Server (assetserver_android.go)

Purpose: Configure base URL for Android asset serving.

Location: v3/internal/assetserver/assetserver_android.go

Build Tag: //go:build android

var baseURL = url.URL{
    Scheme: "https",
    Host:   "wails.localhost",
}

Other Platform Files

All these files have the //go:build android tag:

File Purpose
init_android.go Initialization (no runtime.LockOSThread)
clipboard_android.go Clipboard operations (stub)
dialogs_android.go File/message dialogs (stub)
menu_android.go Menu handling (no-op)
menuitem_android.go Menu items (no-op)
screen_android.go Screen information
mainthread_android.go Main thread dispatch
signal_handler_android.go Signal handling (no-op)
single_instance_android.go Single instance (via manifest)
systemtray_android.go System tray (no-op)
keys_android.go Keyboard handling (stub)
events_common_android.go Event mapping
messageprocessor_android.go Android-specific runtime methods

File Structure

v3/
├── ANDROID_ARCHITECTURE.md          # This document
├── pkg/
│   ├── application/
│   │   ├── application_android.go   # Main Android implementation
│   │   ├── application_options.go   # Contains AndroidOptions struct
│   │   ├── webview_window_android.go
│   │   ├── clipboard_android.go
│   │   ├── dialogs_android.go
│   │   ├── events_common_android.go
│   │   ├── init_android.go
│   │   ├── keys_android.go
│   │   ├── mainthread_android.go
│   │   ├── menu_android.go
│   │   ├── menuitem_android.go
│   │   ├── messageprocessor_android.go
│   │   ├── messageprocessor_mobile_stub.go  # Stub for non-mobile
│   │   ├── screen_android.go
│   │   ├── signal_handler_android.go
│   │   ├── signal_handler_types_android.go
│   │   ├── single_instance_android.go
│   │   └── systemtray_android.go
│   └── events/
│       └── events_android.go
├── internal/
│   └── assetserver/
│       ├── assetserver_android.go
│       └── webview/
│           └── request_android.go
└── examples/
    └── android/
        ├── main.go                  # Application entry point
        ├── greetservice.go          # Example service
        ├── go.mod
        ├── go.sum
        ├── Taskfile.yml             # Build orchestration
        ├── .gitignore
        ├── frontend/                # Web frontend (same as other platforms)
        │   ├── index.html
        │   ├── main.js
        │   ├── package.json
        │   └── ...
        └── build/
            ├── config.yml           # Build configuration
            ├── Taskfile.yml         # Common build tasks
            ├── android/
            │   ├── Taskfile.yml     # Android-specific tasks
            │   ├── build.gradle     # Root Gradle build
            │   ├── settings.gradle
            │   ├── gradle.properties
            │   ├── gradlew          # Gradle wrapper script
            │   ├── gradle/
            │   │   └── wrapper/
            │   │       └── gradle-wrapper.properties
            │   ├── scripts/
            │   │   └── deps/
            │   │       └── install_deps.go  # Dependency checker
            │   └── app/
            │       ├── build.gradle         # App Gradle build
            │       ├── proguard-rules.pro
            │       └── src/
            │           └── main/
            │               ├── AndroidManifest.xml
            │               ├── java/
            │               │   └── com/
            │               │       └── wails/
            │               │           └── app/
            │               │               ├── MainActivity.java
            │               │               ├── WailsBridge.java
            │               │               ├── WailsPathHandler.java
            │               │               └── WailsJSBridge.java
            │               ├── res/
            │               │   ├── layout/
            │               │   │   └── activity_main.xml
            │               │   ├── values/
            │               │   │   ├── strings.xml
            │               │   │   ├── colors.xml
            │               │   │   └── themes.xml
            │               │   └── mipmap-*/  # App icons
            │               ├── assets/        # Frontend assets (copied)
            │               └── jniLibs/
            │                   ├── arm64-v8a/
            │                   │   └── libwails.so  # Generated
            │                   └── x86_64/
            │                       └── libwails.so  # Generated
            ├── darwin/              # macOS build files
            ├── linux/               # Linux build files
            └── windows/             # Windows build files

Implementation Details

Application Startup Flow

1. Android OS launches MainActivity
   │
2. MainActivity.onCreate()
   │
   ├─> WailsBridge.initialize()
   │   │
   │   └─> System.loadLibrary("wails")
   │       │
   │       └─> Go runtime starts
   │           │
   │           └─> nativeInit() called
   │               │
   │               └─> globalApp = app (store reference)
   │
   ├─> setupWebView()
   │   │
   │   ├─> Configure WebSettings
   │   ├─> Create WebViewAssetLoader with WailsPathHandler
   │   ├─> Set WebViewClient for request interception
   │   └─> Add WailsJSBridge via addJavascriptInterface
   │
   └─> loadApplication()
       │
       └─> webView.loadUrl("https://wails.localhost/")
           │
           └─> WailsPathHandler.handle("/")
               │
               └─> WailsBridge.serveAsset("/index.html", ...)
                   │
                   └─> nativeServeAsset() (JNI to Go)
                       │
                       └─> Go AssetServer returns HTML

Asset Request Flow

WebView requests: https://wails.localhost/main.js
        │
        ▼
WebViewClient.shouldInterceptRequest()
        │
        ▼
WebViewAssetLoader.shouldInterceptRequest()
        │
        ▼
WailsPathHandler.handle("/main.js")
        │
        ▼
WailsBridge.serveAsset("/main.js", "GET", "{}")
        │
        ▼
JNI call: nativeServeAsset(path, method, headers)
        │
        ▼
Go: serveAssetForAndroid(app, "/main.js")
        │
        ▼
Go: AssetServer reads from embed.FS
        │
        ▼
Return: byte[] data
        │
        ▼
WailsPathHandler creates WebResourceResponse
        │
        ▼
WebView renders content

JavaScript to Go Message Flow

JavaScript: wails.invoke('{"type":"call","method":"Greet","args":["World"]}')
        │
        ▼
WailsJSBridge.invoke(message)  [@JavascriptInterface]
        │
        ▼
WailsBridge.handleMessage(message)
        │
        ▼
JNI call: nativeHandleMessage(message)
        │
        ▼
Go: handleMessageForAndroid(app, message)
        │
        ▼
Go: Parse JSON, route to service method
        │
        ▼
Go: Execute GreetService.Greet("World")
        │
        ▼
Return: '{"result":"Hello, World!"}'
        │
        ▼
JavaScript receives result

Go to JavaScript Event Flow

Go: app.Event.Emit("time", "Mon, 01 Jan 2024 12:00:00")
        │
        ▼
Go: Call Java executeJavaScript via JNI callback
        │
        ▼
WailsBridge.emitEvent("time", "\"Mon, 01 Jan 2024 12:00:00\"")
        │
        ▼
JavaScript: window.wails._emit('time', "Mon, 01 Jan 2024 12:00:00")
        │
        ▼
Frontend event listeners notified

Build System

Prerequisites

  1. Go 1.21+ with CGO support
  2. Android SDK with:
    • Platform Tools (adb)
    • Build Tools
    • Android Emulator
  3. Android NDK r19c+ (r26d recommended)
  4. Java JDK 11+

Environment Variables

# Required
export ANDROID_HOME=$HOME/Library/Android/sdk    # macOS
export ANDROID_HOME=$HOME/Android/Sdk            # Linux

# Optional (auto-detected if not set)
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125

# Path additions
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/emulator

Taskfile Commands

# Check/install dependencies
task android:install:deps

# Build Go shared library (default: arm64 for device)
task android:build

# Build for emulator (x86_64)
task android:build ARCH=x86_64

# Build for all architectures (fat APK)
task android:compile:go:all-archs

# Package into APK
task android:package

# Run on emulator
task android:run

# View logs
task android:logs

# Clean build artifacts
task android:clean

Build Process Details

1. Go Compilation

# Environment for arm64 (device)
export GOOS=android
export GOARCH=arm64
export CGO_ENABLED=1
export CC=$NDK/toolchains/llvm/prebuilt/$HOST/bin/aarch64-linux-android21-clang

# Build command
go build -buildmode=c-shared \
    -tags android \
    -o build/android/app/src/main/jniLibs/arm64-v8a/libwails.so

2. Gradle Build

cd build/android
./gradlew assembleDebug
# Output: app/build/outputs/apk/debug/app-debug.apk

3. Installation

adb install app-debug.apk
adb shell am start -n com.wails.app/.MainActivity

Architecture Support

Architecture GOARCH JNI Directory Use Case
arm64-v8a arm64 jniLibs/arm64-v8a/ Physical devices (most common)
x86_64 amd64 jniLibs/x86_64/ Emulator
armeabi-v7a arm jniLibs/armeabi-v7a/ Older devices (optional)
x86 386 jniLibs/x86/ Older emulators (optional)

Minimum SDK Configuration

// build/android/app/build.gradle
android {
    defaultConfig {
        minSdk 21        // Android 5.0 (Lollipop) - 99%+ coverage
        targetSdk 34     // Android 14 - Required for Play Store
    }
}

JNI Bridge Details

JNI Function Naming Convention

JNI functions must follow this naming pattern:

Java_<package>_<class>_<method>

Example:

//export Java_com_wails_app_WailsBridge_nativeInit
func Java_com_wails_app_WailsBridge_nativeInit(env *C.JNIEnv, obj C.jobject, bridge C.jobject)

Corresponds to Java:

package com.wails.app;
class WailsBridge {
    private static native void nativeInit(WailsBridge bridge);
}

JNI Type Mappings

Java Type JNI Type Go CGO Type
void void -
boolean jboolean C.jboolean
int jint C.jint
long jlong C.jlong
String jstring *C.char (via conversion)
byte[] jbyteArray *C.char (via conversion)
Object jobject C.jobject

String Conversion

// Java String → Go string
goString := C.GoString((*C.char)(unsafe.Pointer(javaString)))

// Go string → Java String (return)
return C.CString(goString)  // Must be freed by Java

Thread Safety

  • JNI calls must be made from the thread that owns the JNI environment
  • Go goroutines cannot directly call JNI methods
  • Use channels or callbacks to communicate between goroutines and JNI thread

Asset Serving

WebViewAssetLoader Configuration

assetLoader = new WebViewAssetLoader.Builder()
    .setDomain("wails.localhost")           // Custom domain
    .addPathHandler("/", new WailsPathHandler(bridge))  // All paths
    .build();

URL Scheme

  • Base URL: https://wails.localhost/
  • Why HTTPS: Android's WebViewAssetLoader requires HTTPS for security
  • Domain: wails.localhost is arbitrary but consistent with Wails conventions

Path Normalization

// In WailsPathHandler.handle()
if (path.isEmpty() || path.equals("/")) {
    path = "/index.html";
}

MIME Type Detection

MIME types are determined by Go based on file extension. Fallback mapping in Java:

private String getMimeType(String path) {
    if (path.endsWith(".html")) return "text/html";
    if (path.endsWith(".js")) return "application/javascript";
    if (path.endsWith(".css")) return "text/css";
    // ... etc
    return "application/octet-stream";
}

JavaScript Bridge

Exposed Interface

The WailsJSBridge is added to the WebView as:

webView.addJavascriptInterface(new WailsJSBridge(bridge, webView), "wails");

This makes window.wails available in JavaScript.

Security Considerations

  1. @JavascriptInterface annotation is required for all exposed methods (Android 4.2+)
  2. Only specific methods are exposed, not the entire object
  3. Input validation should be performed on all received data

Async Pattern

For non-blocking calls:

// JavaScript side
const callbackId = 'cb_' + Date.now();
window.wails._callbacks[callbackId] = (result, error) => {
    if (error) reject(error);
    else resolve(result);
};
wails.invokeAsync(callbackId, message);

// Java side sends response via:
webView.evaluateJavascript(
    "window.wails._callback('" + callbackId + "', " + result + ", null);",
    null
);

Security Considerations

WebView Security

WebSettings settings = webView.getSettings();
settings.setAllowFileAccess(false);          // No file:// access
settings.setAllowContentAccess(false);       // No content:// access
settings.setMixedContentMode(MIXED_CONTENT_NEVER_ALLOW);  // HTTPS only

JNI Security

  1. No arbitrary code execution: JNI methods have fixed signatures
  2. Input validation: All strings from Java are validated in Go
  3. Memory safety: Go's memory management prevents buffer overflows

Asset Security

  1. Same-origin policy: Assets only served from wails.localhost
  2. No external network: All assets embedded, no remote fetching
  3. Content Security Policy: Can be set via HTML headers

Configuration Options

AndroidOptions Struct

type AndroidOptions struct {
    // DisableScroll disables scrolling in the WebView
    DisableScroll bool

    // DisableOverscroll disables the overscroll bounce effect
    DisableOverscroll bool

    // EnableZoom allows pinch-to-zoom in the WebView (default: false)
    EnableZoom bool

    // UserAgent sets a custom user agent string
    UserAgent string

    // BackgroundColour sets the background colour of the WebView
    BackgroundColour RGBA

    // DisableHardwareAcceleration disables hardware acceleration
    DisableHardwareAcceleration bool
}

Usage

app := application.New(application.Options{
    Name: "My App",
    Android: application.AndroidOptions{
        DisableOverscroll: true,
        BackgroundColour: application.NewRGB(27, 38, 54),
    },
})

AndroidManifest.xml Configuration

<manifest>
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:usesCleartextTraffic="true"  <!-- For localhost -->
        android:hardwareAccelerated="true">

        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|screenSize|keyboardHidden"
            android:windowSoftInputMode="adjustResize">
        </activity>
    </application>
</manifest>

Debugging

Logcat Filtering

# All Wails logs
adb logcat -v time | grep -E "(Wails|WailsBridge|WailsActivity)"

# Using task
task android:logs

WebView Debugging

Enable in debug builds:

if (BuildConfig.DEBUG) {
    WebView.setWebContentsDebuggingEnabled(true);
}

Then in Chrome: chrome://inspect/#devices

Go Debugging

func androidLogf(level string, format string, a ...interface{}) {
    msg := fmt.Sprintf(format, a...)
    println(fmt.Sprintf("[Android/%s] %s", level, msg))
}

Common Issues

  1. "UnsatisfiedLinkError": Library not found or wrong architecture
  2. "No implementation found": JNI function name mismatch
  3. Blank WebView: Asset serving not working, check logcat

API Reference

Go API (Same as Desktop)

// Create application
app := application.New(application.Options{
    Name: "App Name",
    Assets: application.AssetOptions{
        Handler: application.AssetFileServerFS(assets),
    },
    Services: []application.Service{
        application.NewService(&MyService{}),
    },
    Android: application.AndroidOptions{...},
})

// Run (blocks on Android)
app.Run()

// Emit events
app.Event.Emit("eventName", data)

JavaScript API

// Call Go service method
const result = await window.wails.Call.ByName('MyService.Greet', 'World');

// Platform detection
if (window.wails.System.Platform() === 'android') { ... }

// Events
window.wails.Events.On('eventName', (data) => { ... });

Android-Specific Runtime Methods

// Vibrate (haptic feedback)
window.wails.Call.ByName('Android.Haptics.Vibrate', {duration: 100});

// Show toast
window.wails.Call.ByName('Android.Toast.Show', {message: 'Hello!'});

// Get device info
const info = await window.wails.Call.ByName('Android.Device.Info');

Troubleshooting

Build Errors

"NDK not found"

# Set NDK path explicitly
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125

"undefined reference to JNI function"

  • Check function name matches exactly (case-sensitive)
  • Ensure //export comment is directly above function

"cannot find package"

cd examples/android && go mod tidy

Runtime Errors

App crashes on startup

  1. Check logcat for stack trace
  2. Verify library is in correct jniLibs directory
  3. Check architecture matches device/emulator

WebView shows blank

  1. Enable WebView debugging
  2. Check Chrome DevTools for errors
  3. Verify https://wails.localhost/ resolves

JavaScript bridge not working

  1. Check wails object exists: console.log(window.wails)
  2. Verify @JavascriptInterface annotations present
  3. Check for JavaScript errors in console

Future Enhancements

Phase 1: Core Stability

  • Complete JNI callback implementation for Go → Java
  • Full asset server integration
  • Error handling and recovery
  • Unit and integration tests

Phase 2: Feature Parity

  • Clipboard support
  • File dialogs (via Storage Access Framework)
  • Notifications
  • Deep linking

Phase 3: Android-Specific Features

  • Material Design 3 theming integration
  • Edge-to-edge display support
  • Predictive back gesture
  • Picture-in-Picture mode
  • Widgets

Phase 4: Advanced Features

  • Background services
  • Push notifications (FCM)
  • Biometric authentication
  • App Shortcuts
  • Wear OS companion

Conclusion

This architecture provides a solid foundation for Android support in Wails v3. The design prioritizes:

  1. Compatibility: Same Go code runs on all platforms
  2. Performance: No network overhead, native rendering
  3. Security: Sandboxed WebView, validated inputs
  4. Maintainability: Clear separation of concerns

The implementation follows Android best practices while maintaining the simplicity that Wails developers expect. The JNI bridge pattern, while more complex than iOS's CGO approach, provides robust interoperability between Java and Go.

Key Implementation Status

Component Status Notes
Java Activity Complete MainActivity with WebView
JNI Bridge Complete WailsBridge with native methods
Asset Handler Complete WailsPathHandler
JS Bridge Complete WailsJSBridge
Go Platform Files Complete All *_android.go files
Taskfile Complete Build orchestration
Gradle Project Complete App structure
JNI Implementation 🔄 Partial Needs Go → Java callbacks
Asset Server Integration 🔄 Partial Needs full wiring
Testing Pending Needs emulator testing

Document Version: 1.0 Last Updated: November 2024 Wails Version: v3-alpha