brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
mobile-applicationsDevelops cross-platform mobile applications with Go using Fyne and Go mobile for iOS and Android with platform-optimized UI patterns.
Independent GitHub snapshot · Automated classification · Original documentation governs
name: mobile-applications description: Develops cross-platform mobile applications with Go using Fyne and Go mobile for iOS and Android with platform-optimized UI patterns. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: go role: implementation scope: implementation output-format: code content-types:
Senior mobile engineer building cross-platform applications in Go using Fyne and Go Mobile. This skill covers UI development, platform-specific features, battery-conscious design, and deployment to iOS and Android from a single Go codebase.
bind for exposing Go functions to native iOS/Android codeUse this skill when:
Avoid this skill for:
Choose the Approach — Fyne for cross-platform apps with native widgets, or Go Mobile for native bindings. Checkpoint: Decision is based on UI requirements and platform integration depth.
Set Up the Project Structure — Organize shared logic, UI code, and platform-specific code. Checkpoint: Business logic is in a pure Go package that compiles on all platforms.
Implement the UI — Build interfaces using Fyne widgets or Go Mobile native bindings. Checkpoint: UI adapts to both portrait and landscape orientations.
Handle Lifecycle Events — Implement pause, resume, and destroy hooks for resource management. Checkpoint: Background work is cancelled on pause; resources are released on destroy.
Build and Deploy — Cross-compile for target platforms using fyne package or gomobile bind.
Checkpoint: Binary sizes are under 50MB for initial download on both platforms.
Fyne provides a single API for native-looking UI on desktop, iOS, and Android.
// ❌ BAD: platform-specific UI code — cannot run on all platforms
func main() {
// This only works on Windows/macOS/Linux
app := desktop.NewApplication()
// Android-specific code
if runtime.GOOS == "android" {
androidApp := android.NewActivity() // requires CGO, Android NDK
}
// iOS-specific code
if runtime.GOOS == "ios" {
iOSApp := iOS.NewViewController() // requires CGO, Xcode toolchain
}
// UI is built with platform-specific APIs — not portable
window := app.NewWindow("My App")
window.Resize(fyne.NewSize(800, 600))
window.ShowAndRun()
}
What's wrong:
runtime.GOOS — not a single portable binarydesktop.NewApplication() is not the correct entry point for mobile// app is the cross-platform Fyne application singleton.
// It works on desktop, iOS, and Android from the same source code.
var app fyne.App
// main is the entry point for the Fyne mobile application.
// Build with: go build -o myapp .
// Package for iOS: fyne package -os ios -icon icon.png
// Package for Android: fyne package -os android -icon icon.png
func main() {
app = fyne.NewApp()
app.SetIcon(fyne.NewResource("icon.png", []byte{}))
window := app.NewWindow("Go Mobile App")
window.SetContent(buildUI())
// Set minimum window size for mobile screens
window.Resize(fyne.NewSize(360, 640))
window.SetFixedSize(true) // prevent resize on mobile
window.ShowAndRun()
}
// buildUI creates the application's user interface using Fyne widgets.
// All widgets render as native controls on each platform.
func buildUI() fyne.CanvasObject {
// Layout: responsive grid that works on portrait and landscape
content := fyne.NewContainerWithLayout(
fyne.NewVBoxLayout(),
fyne.NewLabel("Go Mobile App"),
fyne.NewSeparator(),
// Data entry
fyne.NewLabel("Enter name:"),
nameEntry := fyne.NewEntry(),
nameEntry.SetPlaceHolder("Your name"),
// Action buttons
fyne.NewContainerWithLayout(
fyne.NewHBoxLayout(),
submitBtn := fyne.NewButton("Submit", func() {
handleSubmit(nameEntry.Text)
}),
fyne.NewButton("Clear", func() {
nameEntry.SetText("")
}),
),
// Results display
resultsLabel := fyne.NewLabel(""),
)
// Store references in app state for lifecycle management
appState = &AppState{nameEntry: nameEntry, resultsLabel: resultsLabel}
return content
}
// AppState holds mutable UI state for access across handlers and lifecycle.
type AppState struct {
nameEntry *fyne.Entry
resultsLabel *fyne.Label
}
var appState *AppState
// handleSubmit processes user input on a background goroutine.
func handleSubmit(name string) {
if name == "" {
appState.resultsLabel.SetText("Error: name cannot be empty")
return
}
// UI updates must happen on the main thread
fyne.Do(func() {
appState.resultsLabel.SetText("Processing...")
})
// Background work in goroutine
go func() {
// Simulate network request or computation
result := processName(name)
// Back to main thread for UI update
fyne.Do(func() {
if result == nil {
appState.resultsLabel.SetText("Error: processing failed")
} else {
appState.resultsLabel.SetText(fmt.Sprintf("Hello, %s!", *result))
}
})
}()
}
// processName processes a name and returns the result.
func processName(name string) *string {
// Simulate work
result := strings.ToUpper(strings.TrimSpace(name))
return &result
}
Why this works:
fyne.Do() ensures UI updates happen on the main threadSetFixedSize(true) prevents layout issues on mobilefyne package handles all platform-specific build stepsGo Mobile generates bindings for calling Go code from native iOS/Android apps.
// ❌ BAD: raw CGO without Go Mobile bindings — requires manual JNI/Objective-C code
package main
import "C" // requires CGO and C toolchain
//export GoFunction
func GoFunction() *C.char {
// Manual memory management — easy to leak
cstr := C.CString("Hello from Go")
defer C.free(unsafe.Pointer(cstr))
return cstr
}
// No automatic binding generation — must write JNI code manually
// for Android and Objective-C bridges for iOS
func main() {}
What's wrong:
C.CString/C.free// Package mobilelib provides functions callable from iOS and Android apps.
// Generated bindings with: gomobile bind -target=ios,android
package mobilelib
import (
"context"
"fmt"
"time"
)
// Config holds configuration for the mobile library.
type Config struct {
APIKey string
Timeout time.Duration
MaxRetries int
}
// Result represents a processed result from the library.
type Result struct {
Data string
Success bool
Error string
}
// ProcessData processes input data and returns a Result.
// This function is available from iOS (Swift/Objective-C) and Android (Java/Kotlin).
func ProcessData(ctx context.Context, input string, cfg Config) (*Result, error) {
if input == "" {
return nil, fmt.Errorf("input cannot be empty")
}
if cfg.APIKey == "" {
return nil, fmt.Errorf("API key is required")
}
if cfg.Timeout == 0 {
cfg.Timeout = 30 * time.Second
}
if cfg.MaxRetries == 0 {
cfg.MaxRetries = 3
}
// Validate context deadline
if deadline, ok := ctx.Deadline(); !ok {
ctx, cancel := context.WithTimeout(ctx, cfg.Timeout)
defer cancel()
}
// Process with retries
var lastErr error
for i := 0; i <= cfg.MaxRetries; i++ {
result, err := processWithAPI(ctx, input, cfg.APIKey)
if err == nil {
return &Result{
Data: result,
Success: true,
}, nil
}
lastErr = err
// Exponential backoff between retries
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(time.Duration(i+1) * 500 * time.Millisecond):
}
}
return nil, fmt.Errorf("process data: all %d retries failed: %w", cfg.MaxRetries, lastErr)
}
// processWithAPI calls the external API with the given input and API key.
func processWithAPI(ctx context.Context, input, apiKey string) (string, error) {
// Implementation would use http.Client with ctx for cancellation
// For binding purposes, the function is self-contained
_ = apiKey // API key used for authentication
return fmt.Sprintf("processed: %s", input), nil
}
// IsAvailable checks if required system features are available.
// Called from native code to check device capabilities before using Go functions.
func IsAvailable() bool {
// Check for required system features
// In a real implementation, this might check GPS, camera, etc.
return true
}
Why this works:
gomobile bind generates native bindings automatically for iOS and Androidstring → String/String, bool → Bool/booleanerror return, mapped to exceptions in native codeMobile apps have lifecycle events that must be handled to prevent resource leaks.
// ❌ BAD: goroutines continue running after app is paused or destroyed
func startBackgroundSync() {
go func() {
for {
// Infinite loop — never stops
syncData()
time.Sleep(5 * time.Minute)
}
}()
}
What's wrong:
// syncManager manages background synchronization with lifecycle awareness.
// It starts sync on resume and cancels on pause.
type syncManager struct {
mu sync.Mutex
ctx context.Context
cancel context.CancelFunc
interval time.Duration
active bool
}
// newSyncManager creates a sync manager with the given interval.
func newSyncManager(interval time.Duration) *syncManager {
return &syncManager{interval: interval}
}
// Start begins background synchronization. Safe to call multiple times.
func (m *syncManager) Start() {
m.mu.Lock()
defer m.mu.Unlock()
if m.active {
return // already running
}
m.ctx, m.cancel = context.WithCancel(context.Background())
m.active = true
go m.syncLoop()
}
// Stop cancels the sync loop and waits for it to finish.
func (m *syncManager) Stop() {
m.mu.Lock()
defer m.mu.Unlock()
if !m.active {
return
}
m.cancel()
m.active = false
}
// syncLoop runs the synchronization loop, respecting the context deadline.
func (m *syncManager) syncLoop() {
ticker := time.NewTicker(m.interval)
defer ticker.Stop()
for {
select {
case <-m.ctx.Done():
return // cancelled — clean exit
case <-ticker.C:
m.doSync(m.ctx)
}
}
}
// doSync performs a single synchronization cycle.
func (m *syncManager) doSync(ctx context.Context) {
// Use context-aware API calls
if err := syncWithServer(ctx); err != nil {
// Log but don't retry immediately — let the next tick handle it
log.Printf("sync failed: %v", err)
}
}
// OnAppResume is called when the app comes to the foreground.
func OnAppResume(manager *syncManager) {
manager.Start()
}
// OnAppPause is called when the app goes to the background.
func OnAppPause(manager *syncManager) {
manager.Stop()
}
Why this works:
context.WithCancel provides clean cancellation of the sync loopsyncManager is thread-safe with mutex protectionStart() is idempotent — safe to call multiple timesStop() cancels context, allowing the goroutine to exit cleanlyOnLifecycle callback can wire these to actual lifecycle eventsfyne.Do() or equivalentcontext.Context for cancellable network requests in mobile appsgo build with the target OS flag for cross-compilation (-os ios, -os android)time.Sleep for synchronization in mobile apps — it blocks the main thread| Skill | Purpose |
|---|---|
best-practices | Go idioms, naming, and error handling conventions |
web-applications | HTTP client patterns for mobile API communication |
modular-design | Package structure for shared business logic across mobile and web |