Skip to content

Instantly share code, notes, and snippets.

@aabiji
Created August 12, 2025 21:16
Show Gist options
  • Select an option

  • Save aabiji/254f27c987d99a58d1f4e949842e48d4 to your computer and use it in GitHub Desktop.

Select an option

Save aabiji/254f27c987d99a58d1f4e949842e48d4 to your computer and use it in GitHub Desktop.
//go:generate javac -classpath $ANDROID_HOME/platforms/android-36/android.jar -d /tmp/java_classes Utils.java
//go:generate jar cf Utils.jar -C /tmp/java_classes .
package main
/*
#cgo LDFLAGS: -llog -landroid
#include <android/log.h>
#include <android/native_window_jni.h>
#include <stdlib.h>
static jint jni_GetEnvOrAttach(JavaVM *vm, JNIEnv **env, jint *attached) {
jint res = (*vm)->GetEnv(vm, (void **)env, JNI_VERSION_1_6);
if (res == JNI_EDETACHED) {
res = (*vm)->AttachCurrentThread(vm, (void **)env, NULL);
*attached = res == JNI_OK;
}
return res;
}
static void jni_DetachCurrent(JavaVM *vm) {
(*vm)->DetachCurrentThread(vm);
}
*/
import "C"
import (
"fmt"
"log"
"os"
"runtime"
"runtime/debug"
"strings"
"unsafe"
"gioui.org/app"
"gioui.org/font/gofont"
"gioui.org/op"
"gioui.org/text"
"gioui.org/widget/material"
"github.com/timob/jnigi"
)
func getJNIEnv() (*jnigi.Env, func()) {
runtime.LockOSThread()
jvm := app.JavaVM()
cJVM := (*C.JavaVM)(unsafe.Pointer(jvm))
var cEnv *C.JNIEnv
var attached C.jint
C.jni_GetEnvOrAttach(cJVM, &cEnv, &attached)
_, env := jnigi.UseJVM(unsafe.Pointer(jvm), unsafe.Pointer(cEnv), nil)
cleanup := func() {
if attached != 0 {
C.jni_DetachCurrent(cJVM)
}
runtime.UnlockOSThread()
}
return env, cleanup
}
func writeToDownloadsFolder(filename, mimetype string, contents []byte) error {
env, cleanup := getJNIEnv()
defer cleanup()
context := jnigi.WrapJObject(app.AppContext(), "android/content/Context", false)
filenameObj, err := env.NewObject("java/lang/String", []byte(filename))
if err != nil {
return err
}
mimetypeObj, err := env.NewObject("java/lang/String", []byte(mimetype))
if err != nil {
return err
}
contentsObj := env.NewByteArrayFromSlice(contents)
err = env.CallStaticMethod(
"com/example/demo/Utils",
"writeToDownloadsFolder",
nil, // returns void
context, contentsObj, filenameObj, mimetypeObj,
)
return err
}
func androidCrashHandler() {
if r := recover(); r != nil {
str := fmt.Sprintf("Crash: %v\n%s", r, debug.Stack())
tag := C.CString("demo-project")
defer C.free(unsafe.Pointer(tag))
lines := strings.Split(str, "\n")
for _, line := range lines {
msg := C.CString(line)
C.__android_log_write(C.ANDROID_LOG_INFO, tag, msg) // from <android/log.h>
C.free(unsafe.Pointer(msg))
}
}
}
func main() {
if runtime.GOOS == "android" {
defer androidCrashHandler()
contents := []byte("Open your mind...")
if err := writeToDownloadsFolder("this-works.txt", "text/plain", contents); err != nil {
panic(fmt.Sprintf("Failed to write to Downloads: %s", err))
}
}
go func() {
w := new(app.Window)
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func loop(w *app.Window) error {
th := material.NewTheme()
th.Shaper = text.NewShaper(text.WithCollection(gofont.Collection()))
var ops op.Ops
for {
switch e := w.Event().(type) {
case app.DestroyEvent:
return e.Err
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
l := material.H1(th, "Hello world!")
l.Alignment = text.Middle
l.Layout(gtx)
e.Frame(gtx.Ops)
}
}
}
package com.example.demo;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import java.io.OutputStream;
public class Utils {
public static void writeToDownloadsFolder(
Context context, byte[] contents, String filename, String mimetype) {
try {
ContentResolver resolver = context.getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, filename);
values.put(MediaStore.MediaColumns.MIME_TYPE, mimetype);
values.put(MediaStore.MediaColumns.RELATIVE_PATH,
Environment.DIRECTORY_DOWNLOADS + "/");
Uri uri = resolver.insert(MediaStore.Files.getContentUri("external"), values);
OutputStream output = resolver.openOutputStream(uri);
output.write(contents);
output.flush();
output.close();
} catch (Exception e) {
Log.e("demo-project", "Exception occurred!", e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment