Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,33 @@ set(YYJSON_INSTALL OFF)
add_subdirectory(src/c_src/yyjson EXCLUDE_FROM_ALL)
set_target_properties(yyjson PROPERTIES POSITION_INDEPENDENT_CODE ON)

# ============================================================================
# Android backend
# ============================================================================
if(ANDROID)
add_library(ring_webview_android OBJECT
src/c_src/ring_webview_android.c
)
set_target_properties(ring_webview_android PROPERTIES
POSITION_INDEPENDENT_CODE ON
)
if(TARGET ring)
target_link_libraries(ring_webview_android PUBLIC ring)
else()
target_include_directories(ring_webview_android PUBLIC
"${RING_ROOT}/language/include"
)
endif()
target_link_libraries(ring_webview_android PUBLIC
android
log
yyjson
)
message(STATUS "Ring Webview: Android backend")
return()
endif()


if(NOT TARGET Ring::Ring)
add_library(Ring::Ring UNKNOWN IMPORTED)
find_path(RING_INCLUDE_DIR ring.h PATHS "${RING_ROOT}/language/include")
Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ This project is made possible by the tiny [webview](https://github.com/webview/w

## ✨ Features

- **Cross-Platform:** Build applications for Windows, macOS, Linux, and FreeBSD from a single codebase.
- **Cross-Platform:** Build applications for Windows, macOS, Linux, FreeBSD, and Android from a single codebase.
- **Modern UI:** Use familiar web technologies to design your user interface.
- **Two-Way Binding:** Seamlessly call Ring functions from JavaScript and vice-versa.
- **Easy to Use:** A simple and clean API makes it easy to get started.
Expand Down Expand Up @@ -93,6 +93,15 @@ The compiled macOS library in this package uses the built-in WebKit framework.

</details>

<details>
<summary>Click here for instructions on <img width="20" height="20" src="https://developer.android.com/static/images/favicon-v2.png" /> Android</summary>

Android apps are built with [ring2apk](https://github.com/ysdragon/ring2apk) (no Gradle).
You need the Android SDK, the NDK, a JDK, and CMake + Ninja.
See [📱 Android](#-android) and [`docs/ANDROID.md`](docs/ANDROID.md) for the full guide.

</details>

- **Install the library using RingPM:**
```sh
ringpm install webview from ysdragon
Expand Down Expand Up @@ -153,6 +162,25 @@ ring main.ring

See [`examples/templates/`](examples/templates/) for details.

## 📱 Android

The same `main.ring` runs on Android via [ring2apk](https://github.com/ysdragon/ring2apk)
— no Gradle. See [`examples/android/`](examples/android/) (a
glassmorphism notes app) and [`docs/ANDROID.md`](docs/ANDROID.md) (backend
architecture, lifecycle, recovery).

See [`docs/ANDROID.md`](docs/ANDROID.md) to build and run it.

<div align="center">
<a href="examples/android/">
<img src="examples/android/img/nota-screenshot.png" alt="Nota notes app on Android" width="300">
</a>
<br>
<sub>
<a href="examples/android/">Nota notes app on Android</a>
</sub>
</div>

## 📚 API Reference

For a detailed list of all available functions, classes, and methods, please refer to our [API reference documentation](docs/REFERENCE.md).
Expand Down
74 changes: 74 additions & 0 deletions docs/ANDROID.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Building Android Apps with Ring WebView

Write `main.ring` once, ship it on desktop and Android. On Android the same
Ring C-API is backed by `android.webkit.WebView` through JNI — no Java and
no Gradle required from you.

## Prerequisites

- Ring 1.27+, Android SDK + NDK + JDK, CMake + Ninja
- [ring2apk](https://github.com/ysdragon/ring2apk): `ringpm install ring2apk from ysdragon`

## Start

```sh
ring2apk init myapp # scaffold, or copy examples/android/
cd myapp
ring2apk build # → build/myapp-debug.apk
ring2apk run # build + install + launch + logcat
adb logcat -s RingOutput:D # `see` output
```

Config lives in `ring2apk.ring` (`:packageId`, `:targets`, `:permissions`,
`:entryPoint`). The working reference is
[`examples/android/`](../examples/android/) — a notes
app; its [README](../examples/android/README.md) covers the layout.

## Write the app

`ring/main.ring` is compiled to embedded bytecode and linked into
`libmain.so` — no `.ring` files ship. The API matches desktop, so most of
[USAGE](USAGE.md) and [REFERENCE](REFERENCE.md) apply as-is:

```ring
load "webview.ring"

aBindList = [["getState", :fetchNotes]]

oWebView = new WebView()
oWebView {
setTitle("My App")
onLoad(:handleLoad)
onDomReady(:handleDomReady)
onClose(:handleClose)
bindMany(NULL)
setHtml(`<h1>Hello Android</h1><script>...</script>`)
run()
}

func fetchNotes(cId, aReq)
oWebView.wreturn(cId, WEBVIEW_ERROR_OK, aNotes)
```

Bridge rules that matter on mobile:

- Binds receive Ring lists (JS argument array, JSON-decoded) and should
`wreturn` results; JS gets promises. See the example's bridge table.
- Drive the first render from `onDomReady` (`evalJS("refresh()")`) — it runs
strictly after the bind shim is injected.
- Files persist in the app's `filesDir` (the worker starts there); use
relative paths.
- Window-management calls (`setSize`, `minimize`, …) are desktop no-ops
that return safely — guard mobile layouts with responsive CSS instead.

## Under the hood

UI thread owns the WebView and only enqueues; a worker thread owns the Ring
VM and runs a bounded job queue. Rotation re-attaches without destroying;
a killed renderer rebuilds the view and reloads the last HTML/URL (poison
pages stop after 3 crashes in 10s). Single webview per process.
`RingBridge` is callable by any loaded page JS, remote included — validate
bind arguments.

Backend sources: `src/c_src/ring_webview_android.c`,
`src/android/io/github/ysdragon/webview/MainActivity.java`.
3 changes: 3 additions & 0 deletions examples/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src/cpp/ring
build/
.ring2apk.hash
52 changes: 52 additions & 0 deletions examples/android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Nota — Ring WebView on Android

A real notes app with a glassmorphism mobile UI, built with Ring + WebView.
Same-language backend as desktop: Ring owns the state, JavaScript renders it.

<img src="img/nota-screenshot.png" alt="Nota on Android" width="300">

## What it does

- Add, edit, delete, pin, and search notes
- Color-coded glass cards, bottom-sheet editor, toasts
- Persists to `notes.db` in the app's `filesDir` (survives restarts)
- Survives rotation and renderer kills (system memory pressure)

## Bridge pattern

Ring never touches JSON text. Binds receive Ring lists (decoded from the
JS argument array) and `wreturn` the full notes list back; JS re-renders
from the promise resolution:

| JS call | Ring callback |
|---|---|
| `getState()` | `fetchNotes` |
| `note_save([id, title, body, color, pinned])` | `storeNote` |
| `note_delete([id])` | `removeNote` |
| `note_pin([id])` | `toggleNotePin` |

The first pull is driven by native `onDomReady` (`evalJS("refresh()")`),
which runs strictly after the bind shim is injected — no polling.

## Build

Needs [ring2apk](https://github.com/ysdragon/ring2apk):

```sh
ringpm install ring2apk from ysdragon
cd examples/android
ring2apk build # → build/webviewdemo-debug.apk
ring2apk run # build + install + launch + logcat
adb logcat -s RingOutput:D # `see` output
```

## Layout

```
ring/main.ring # app: state, binds, persistence, embedded UI
ring/fulltest.ring # backend conformance suite (20 checks)
ring/src/ # webview.ring loader (same API as desktop)
src/cpp/ # libmain.so: Ring VM + backend (ring/ vendored, ignored by git)
src/java/ # MainActivity mirror (canonical copy in src/android/)
ring2apk.ring # package io.github.ysdragon.webview, arm64-v8a
```
Binary file added examples/android/img/nota-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/android/res/mipmap-hdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/android/res/mipmap-mdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/android/res/mipmap-xhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions examples/android/res/values/colors.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
</resources>
4 changes: 4 additions & 0 deletions examples/android/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Ring WebView</string>
</resources>
15 changes: 15 additions & 0 deletions examples/android/res/values/styles.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Edge-to-edge theme: the window draws behind the system bars and the
status bar background is transparent, so web content is visible
underneath the status bar icons.

windowDrawsSystemBarBackgrounds is required for statusBarColor to
take effect; without it the system draws an opaque black scrim.
-->
<style name="AppTheme" parent="@android:style/Theme.NoTitleBar">
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>
Loading
Loading