Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(android): generate kotlin files at build time #671

Merged
merged 10 commits into from
Aug 24, 2022
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
5 changes: 5 additions & 0 deletions .changes/kotlin-files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wry": "minor"
---

WRY will now generate the needed kotlin files at build time but you need to set `WRY_ANDROID_REVERSED_DOMAIN`, `WRY_ANDROID_APP_NAME_SNAKE_CASE` and `WRY_ANDROID_KOTLIN_FILES_OUT_DIR` env vars.
18 changes: 17 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ jobs:
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }
- { target: x86_64-apple-darwin, os: macos-latest }
- { target: aarch64-apple-ios, os: macos-latest }
- { target: aarch64-linux-android, os: ubuntu-latest }
- {
target: aarch64-linux-android,
os: ubuntu-latest,
env: {
WRY_ANDROID_REVERSED_DOMAIN: "app.tauri",
WRY_ANDROID_APP_NAME_SNAKE_CASE: "wry",
WRY_ANDROID_KOTLIN_FILES_OUT_DIR: "out",
}
}

runs-on: ${{ matrix.platform.os }}

Expand Down Expand Up @@ -74,8 +82,16 @@ jobs:
${{ matrix.platform }}-stable-cargo-core-${{ hashFiles('Cargo.toml') }}
${{ matrix.platform }}-stable-cargo-core-

- name: create kotlin out dir
if: contains(matrix.platform.target, 'android')
run: mkdir out

- name: build wry
run: cargo build --features tray --target ${{ matrix.platform.target }}
env:
WRY_ANDROID_REVERSED_DOMAIN: ${{ matrix.platform.env.WRY_ANDROID_REVERSED_DOMAIN }}
WRY_ANDROID_APP_NAME_SNAKE_CASE: ${{ matrix.platform.env.WRY_ANDROID_APP_NAME_SNAKE_CASE }}
WRY_ANDROID_KOTLIN_FILES_OUT_DIR: ${{ matrix.platform.env.WRY_ANDROID_KOTLIN_FILES_OUT_DIR }}

- name: build tests and examples
shell: bash
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ WebView2 provided by Microsoft Edge Chromium is used. So wry supports Windows 7,

We have experimental support of mobile ends. If you are interested in playing or hacking it, please follow this [note](https://hackmd.io/XIcEwk4GSxy8APZhSa0UnA?view).

When building for Android, WRY generates kotlin files that are needed to run WRY on Android and you have to set the following environment variables:
- `WRY_ANDROID_REVERSED_DOMAIN`
- `WRY_ANDROID_APP_NAME_SNAKE_CASE`
- `WRY_ANDROID_KOTLIN_FILES_OUT_DIR`

You can skip setting these environment variables if you are using the WRY template from our [`cargo-mobile`](https://github.com/tauri-apps/cargo-mobile) fork.

## License

Apache-2.0/MIT
57 changes: 57 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,61 @@ fn main() {
if is_macos {
println!("cargo:rustc-link-lib=framework=WebKit");
}

let is_android = std::env::var("CARGO_CFG_TARGET_OS")
.map(|t| t == "android")
.unwrap_or_default();
if is_android {
use std::{fs, path::PathBuf};

fn env_var(var: &str) -> String {
amrbashir marked this conversation as resolved.
Show resolved Hide resolved
std::env::var(var).expect(&format!(
" `{}` is not set, which is needed to generate the kotlin files for android.",
var
))
}

println!("cargo:rerun-if-env-changed=WRY_ANDROID_REVERSED_DOMAIN");
println!("cargo:rerun-if-env-changed=WRY_ANDROID_APP_NAME_SNAKE_CASE");
println!("cargo:rerun-if-env-changed=WRY_ANDROID_KOTLIN_FILES_OUT_DIR");

let reversed_domain = env_var("WRY_ANDROID_REVERSED_DOMAIN");
let app_name_snake_case = env_var("WRY_ANDROID_APP_NAME_SNAKE_CASE");
let kotlin_out_dir = env_var("WRY_ANDROID_KOTLIN_FILES_OUT_DIR");

let kotlin_out_dir = PathBuf::from(kotlin_out_dir)
.canonicalize()
.expect("Failed to canonicalize path");
let kotlin_files =
fs::read_dir(PathBuf::from(env_var("CARGO_MANIFEST_DIR")).join("src/webview/android/kotlin"))
.expect("failed to read kotlin directory");

for file in kotlin_files {
let file = file.unwrap();

let class_extension_env = format!(
"WRY_{}_CLASS_EXTENSION",
file
.path()
.file_stem()
.unwrap()
.to_string_lossy()
.to_uppercase()
);

println!("cargo:rerun-if-env-changed={}", class_extension_env);

let content = fs::read_to_string(file.path())
.expect("failed to read kotlin file as string")
.replace("{{app-domain-reversed}}", &reversed_domain)
.replace("{{app-name-snake-case}}", &app_name_snake_case)
.replace(
"{{class-extension}}",
&std::env::var(&class_extension_env).unwrap_or_default(),
);

fs::write(kotlin_out_dir.join(file.file_name()), content)
.expect("Failed to write kotlin file");
}
}
}
20 changes: 20 additions & 0 deletions src/webview/android/kotlin/Ipc.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package {{app-domain-reversed}}.{{app-name-snake-case}}

import android.webkit.*

class Ipc {
@JavascriptInterface
fun postMessage(message: String) {
this.ipc(message)
}

companion object {
init {
System.loadLibrary("{{app-name-snake-case}}")
}
}

private external fun ipc(message: String)

{{class-extension}}
}
36 changes: 36 additions & 0 deletions src/webview/android/kotlin/RustWebViewClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package {{app-domain-reversed}}.{{app-name-snake-case}}

import android.graphics.Bitmap
import android.webkit.*

class RustWebViewClient(initScripts: Array<String>): WebViewClient() {
private val initializationScripts: Array<String>

init {
initializationScripts = initScripts
}

override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
for (script in initializationScripts) {
view?.evaluateJavascript(script, null)
}
super.onPageStarted(view, url, favicon)
}

override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
return handleRequest(request)
}

companion object {
init {
System.loadLibrary("{{app-name-snake-case}}")
}
}

private external fun handleRequest(request: WebResourceRequest): WebResourceResponse?

{{class-extension}}
}
73 changes: 73 additions & 0 deletions src/webview/android/kotlin/TauriActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package {{app-domain-reversed}}.{{app-name-snake-case}}
amrbashir marked this conversation as resolved.
Show resolved Hide resolved

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

abstract class TauriActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
create(this)
}

override fun onStart() {
super.onStart()
start()
}

override fun onResume() {
super.onResume()
resume()
}

override fun onPause() {
super.onPause()
pause()
}

override fun onStop() {
super.onStop()
stop()
}

override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
focus(hasFocus)
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
save()
}

override fun onDestroy() {
super.onDestroy()
destroy()
}

override fun onLowMemory() {
super.onLowMemory()
memory()
}

fun getAppClass(name: String): Class<*> {
return Class.forName(name)
}

companion object {
init {
System.loadLibrary("{{app-name-snake-case}}")
}
}

private external fun create(activity: TauriActivity)
private external fun start()
private external fun resume()
private external fun pause()
private external fun stop()
private external fun save()
private external fun destroy()
private external fun memory()
private external fun focus(focus: Boolean)

{{class-extension}}
}