Skip to content

Commit

Permalink
feat(android): generate kotlin files at build time (#671)
Browse files Browse the repository at this point in the history
* feat(android): generate kotlin files at build time

* changefile

* Update kotlin-files.md

* fix android ci

* Add option for extra code when generating files

* rerun build script when env changes

* change it to a class code, prepare var name for future additions

* Delete MainActivity.kt

* uppercase [skip ci]

* fix android detection

Co-authored-by: Lucas Nogueira <[email protected]>
  • Loading branch information
amrbashir and lucasfernog authored Aug 24, 2022
1 parent 1b26d60 commit b478903
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 1 deletion.
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 {
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}}

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}}
}

0 comments on commit b478903

Please sign in to comment.