--- START OF POST ---
URL: https://blog.kingsword.tech/2025/09/27/a-gradle-plugin-for-high-performance-material-symbols-in-kmp/
Published At: 2025-09-27T00:00:00.000Z
# Kotlin Multiplatform 项目的高性能 Material Symbols 生成插件
最近在开发 Kotlin Multiplatform App 时,需要使用一些图标。很自然地想到了 `material-icons-extended` 库,但集成后发现了一个严重的问题:无论怎么使用 `Proguard` 进行混淆压缩,App 的图标包体积始终会增加 11.3 MB。这对于追求轻量化的现代应用来说,是难以接受的。
为了解决这个问题,我决定寻找一个可以按需引入、只包含项目中实际使用到的图标的解决方案。调研中发现 Google 在 2022 年推出了新的图标系列 [Material Symbols](https://fonts.google.com/icons),它提供了更丰富的样式和配置。于是,我开发了一款名为 **SymbolCraft** 的 Gradle 插件,旨在彻底解决 KMP 项目中的图标体积问题。
[SymbolCraft](https://github.com/kingsword09/SymbolCraft) 是一个为 Kotlin Multiplatform 设计的、支持按需生成 Material Symbols 图标的 Gradle 插件。它通过分析您的配置,自动从 CDN (借助 [esm.sh](https://esm.sh)) 下载指定图标库`@material-symbols/svg-[100-700]`的 SVG 文件,并利用 [DevSrSouza/svg-to-compose](https://github.com/DevSrSouza/svg-to-compose) 库将其转换为高性能的 `ImageVector`。
除了解决体积问题,SymbolCraft 还着力于优化开发体验和构建性能。它内置了 **智能缓存** 机制,避免重复下载;利用 Kotlin 协程实现 **并行下载**,加快生成速度;并保证 **确定性构建**,生成的代码完全一致,对版本控制和 Gradle 的构建缓存都非常友好。
当然,它也全面支持 Material Symbols 丰富的样式选项(粗细、风格、填充),并能生成高质量的矢量图形。其中我最喜欢的功能之一是 **Compose 预览**,插件可以自动为所有生成的图标创建 `@Preview` 函数,让你在 IDE 里就能一目了然,极大提升了开发效率。
### 那么,如何开始使用?
集成 SymbolCraft 的过程非常流畅。首先,像添加其他 Gradle 插件一样,在 `libs.versions.toml` 中定义它:
```toml
[plugins]
symbolCraft = { id = "io.github.kingsword09.symbolcraft", version = "0.1.1" }
```
接着在你的模块 `build.gradle.kts` 文件中应用这个插件:
```kotlin
plugins {
alias(libs.plugins.symbolCraft)
}
```
最关键的一步来了:通过 `materialSymbols` DSL 配置块告诉 SymbolCraft 你需要哪些图标。它的配置非常灵活,既可以为单个图标精细定义样式,也支持便捷的批量操作。如果你不确定某个图标的名称,或者想浏览所有可用的图标,可以访问 [Material Symbols Demo](https://marella.github.io/material-symbols/demo/) 这个网站,它非常方便:
```kotlin
materialSymbols {
// 基础配置
packageName.set("com.app.symbols")
outputDirectory.set("src/commonMain/kotlin") // 支持多平台项目
cacheEnabled.set(true)
// (可选) 开启 Compose 预览生成
generatePreview.set(true)
// 配置单个图标,比如 "search"
symbol("search") {
style(weight = 400, variant = SymbolVariant.OUTLINED, fill = SymbolFill.UNFILLED)
style(weight = 500, variant = SymbolVariant.OUTLINED, fill = SymbolFill.FILLED)
}
// 使用便捷的批量配置方法
symbol("home") {
standardWeights() // 自动添加 400, 500, 700 三种粗细
}
symbol("person") {
allVariants(weight = 400) // 添加所有变体 (outlined, rounded, sharp)
}
// 批量配置多个图标
symbols("favorite", "star", "bookmark") {
weights(400, 500, variant = SymbolVariant.OUTLINED)
}
}
```
### 从配置到代码:完整的工作流是怎样的?
配置完成后,我们就可以让 SymbolCraft 大显身手了。只需在终端运行一个简单的 Gradle 命令:
```bash
./gradlew generateMaterialSymbols
```
> 如果遇到缓存问题或想强制重新生成所有图标,可以加上 `--rerun-tasks` 参数。
插件便会开始工作,你会看到它高效地下载 SVG、命中缓存、并最终生成代码。任务成功后,在你指定的输出目录(例如 `src/commonMain/kotlin/com/app/symbols`)下,就会出现所有图标对应的 Kotlin 文件。
接下来,在你的 Composable 函数中就可以直接引用这些新鲜出炉的 `ImageVector` 了:
```kotlin
import com.app.symbols.MaterialSymbols
import com.app.symbols.materialsymbols.SearchW400Outlined
import com.app.symbols.materialsymbols.HomeW700Rounded
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
@Composable
fun MyScreen() {
// 你可以直接导入使用
Icon(
imageVector = SearchW400Outlined,
contentDescription = "Search"
)
// 或者通过 MaterialSymbols 对象访问,方便查找
Icon(
imageVector = MaterialSymbols.HomeW700Rounded,
contentDescription = "Home"
)
}
```
但我们如何确认生成的图标就是我们想要的样式呢?总不能每次都运行 App 查看吧。这正是 `generatePreview` 功能的用武之地。开启后,SymbolCraft 会额外生成一个预览文件,让你在 Android Studio 或 IntelliJ IDEA 的预览面板中就能一览所有图标的实际效果,极大地方便了调试和验证。
### 总结
SymbolCraft 通过按需生成和智能缓存的策略,成功解决了在 KMP 项目中使用 Material Symbols 导致的包体积膨胀问题。它不仅提供了强大的功能和灵活的配置,还通过 Compose 预览等特性优化了开发体验。如果你也在为 KMP 项目的图标方案而烦恼,不妨试试 SymbolCraft!
#### 参考文档
- [SymbolCraft GitHub Repository](https://github.com/kingsword09/SymbolCraft)
- [Material Symbols - Google Fonts](https://fonts.google.com/icons)
- [Material Symbols Demo - by marella](https://marella.github.io/material-symbols/demo/)
--- END OF POST ---
--- START OF POST ---
URL: https://blog.kingsword.tech/2025/04/19/github-commit-gpg-signing/
Published At: 2025-04-19T00:00:00.000Z
# GitHub提交签名验证
最近在 `GitHub` 贡献 `Pull Request` 时,发现贡献者需要提交签名验证,因此记录下这个过程。
以 `Mac` 电脑为例:
#### GPG
##### 1. 安装 `GPG` 程序
```bash
brew install gnupg
```
> 如果是 windows 则安装 [Gpg4win](https://www.gpg4win.org/)。
##### 2. 生成 `GPG` 密钥
(1) 生成密钥
```bash
gpg --full-generate-key
```
> 使用默认加密即可(椭圆曲线ECC)
>
> 电子邮箱需要填写经过 `GitHub` 验证的邮箱
(2) 先获取 `GPG` 密钥 `ID`
```bash
$ gpg --list-secret-keys --keyid-format=long
/Users/hubot/.gnupg/secring.gpg
------------------------------------
sec 4096R/3AA5C34371567BD2 2016-03-10 [expires: 2017-03-10]
uid Hubot
ssb 4096R/4BB6D45482678BE3 2016-03-10
```
(3) 获取公钥
```bash
gpg --armor --export 3AA5C34371567BD2
# Prints the GPG key ID, in ASCII armor format
```
(4) 复制以 `-----BEGIN PGP PUBLIC KEY BLOCK-----` 开头并以 `-----END PGP PUBLIC KEY BLOCK-----` 结尾的 GPG 密钥。
(5) [将 `GPG` 密钥新增到 `GitHub` 帐户](https://docs.github.com/zh/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account)
- 在 GitHub 任意页面的右上角,单击个人资料照片,然后单击 “设置”****。
- 在边栏的“访问”部分中,单击 “SSH 和 GPG 密钥”。
- 在“GPG 密钥”标头旁边,单击“新建 GPG 密钥”。
- 在“标题”字段中键入 GPG 密钥的名称。
- 在“密钥”字段中,粘贴生成 GPG 密钥时复制的 GPG 密钥。
- 单击“添加 GPG 密钥”
- 若要确认操作,请向 GitHub 帐户进行身份验证以确认操作。
(6) `git` `commit` 配置 `signing`
- 全局配置
```bash
git config --global user.signingkey 4874E921904AC200
git config --global commit.gpgsign true
```
- 项目配置
```bash
git config user.signingkey 4874E921904AC200
git config commit.gpgsign true
```
##### 3. 配置 `GPG`
因为平时使用 `GitHub Desktop` 提交代码,需要有一个可以输入 `GPG` `passphrases` 的 `GUI` 程序,如果使用命令行则不需要,因为默认的 `pinentry` 可以在命令行中出现 `dialog` 来输入。
(1) 安装 `pinentry` `GUI` 程序
```bash
brew install pinentry-mac
```
(2) 配置 `gpg-agent.conf` 用于控制弹出 `pinentry` 的频次和程序
`gpg-agent.conf` 位置:
- `Mac`:`~/.gnupg/gpg-agent.conf`
```bash
# Set the default cache time to 1 day.
default-cache-ttl 86400
# Set the max cache time to 30 days.
max-cache-ttl 2592000
# Set pinentry GUI program (Only MacOS)
pinentry-program /opt/homebrew/bin/pinentry-mac
```
- `Windows`: `C:\Users\\AppData\Roaming\gnupg\gpg-agent.conf`
> powershell可以使用 `cd $env:AppData/gnupg` 进入 `gnupg` 配置目录
```bash
# Set the default cache time to 1 day.
default-cache-ttl 86400
# Set the max cache time to 30 days.
max-cache-ttl 2592000
```
##### 4. 重启 `gpg-agent`
```bash
gpgconf --kill gpg-agent
gpg-connect-agent reloadagent /bye
```
#### QA
- `gpg: signing failed: Inappropriate ioctl for device`
出现这个错误是因为没有弹出 `pinentry` `GUI` 程序输入 `passphrases`。
- 如何备份和导入 `GPG` 配置
[备份和恢复 `GPG` `key`](https://www.jwillikers.com/backup-and-restore-a-gpg-key)
#### 参考文档
- [生成 `GPG` 密钥](https://docs.github.com/zh/authentication/managing-commit-signature-verification/generating-a-new-gpg-key)
- [将 `GPG` 密钥添加到 `GitHub` 帐户](https://docs.github.com/zh/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account)
- [备份和恢复 `GPG` `key`](https://www.jwillikers.com/backup-and-restore-a-gpg-key)
- [How can I get GPG Agent to cache my password?](https://askubuntu.com/questions/805459/how-can-i-get-gpg-agent-to-cache-my-password)
--- END OF POST ---
--- START OF POST ---
URL: https://blog.kingsword.tech/2025/04/13/tauri2-plugin-developerment-pitfall-record-3/
Published At: 2025-04-13T00:00:00.000Z
# Tauri 2.0 插件开发踩坑记录(3)
最近使用 `Tauri 2.0` 封装 `web` 前端为 `app` 时,有个需求就是在 `Android` 应用内授权安装应用,但是在开发过程中遇到了一些问题,本文将详细介绍这些问题以及解决方案。
假设要开发的插件为: [tauri-plugin-android-package-install](https://github.com/kingsword09/tauri-plugins-workspace/tree/main/packages/tauri-plugin-android-package-install)。
在 `TypeScript` 的接口定义中就可以以下面的方式进行定义:
`guest-js/index.ts`:
```ts
import { invoke } from "@tauri-apps/api/core";
export async function install(installPath: string): Promise {
await invoke("plugin:android-package-install|install", {
installPath,
});
}
```
在 `Rust` 中定义的命令如下:
```rust
#[command]
pub(crate) async fn install(app: AppHandle, install_path: String) -> Result<()> {
app.android_package_install().install(install_path)
}
```
在这里就遇到了第一个问题:因为 `Rust` 中定义的参数名是 `install_path`,则 `TypeScript` 中定义必须使用 `installPath`,即 `invoke` 的 `args` 参数名必须使用 `installPath`,否则会出现以下错误:
```shell
Msg: Uncaught (in promise) invalid args `installPath` for command `install`: command install missing required key installPath
```
代码如下:
```ts
// ❌错误
import { invoke } from "@tauri-apps/api/core";
export async function install(install_path: string): Promise {
await invoke("plugin:android-package-install|install", {
install_path,
});
}
// ✅正确
import { invoke } from "@tauri-apps/api/core";
export async function install(installPath: string): Promise {
await invoke("plugin:android-package-install|install", {
installPath,
});
}
// ✅正确
import { invoke } from "@tauri-apps/api/core";
export async function install(install_path: string): Promise {
await invoke("plugin:android-package-install|install", {
installPath: install_path,
});
}
```
第二个可能遇到的问题是安装问题,即:
```shell
FileProvider Issue: Failed to find configured root that contains
```
需要在 `res/xml/file_paths.xml` 配置 `files-path`,即:
```diff
```
具体其它细节就不再赘述了,可以看 [tauri-plugin-android-package-install](https://github.com/kingsword09/tauri-plugins-workspace/tree/main/packages/tauri-plugin-android-package-install) 详细代码。
#### 参考文档
- [Tauri 官方文档](https://v2.tauri.app/)
- [tauri-plugin-android-package-install](https://github.com/kingsword09/tauri-plugins-workspace/tree/main/packages/tauri-plugin-android-package-install)
--- END OF POST ---
--- START OF POST ---
URL: https://blog.kingsword.tech/2025/04/06/tauri2-deno-starter-record-1/
Published At: 2025-04-06T00:00:00.000Z
# Tauri 2.0 项目deno workspace管理依赖(1)
最近在开发 `Tauri 2.0` 应用时,需要开发自己的插件,但是插件开发需要使用 `TypeScript` 开发之后还需要打包才能使用,这很麻烦。于是想着要利用 `deno` 的能力,直接就可以将 `TypeScript` 包直接使用在前端项目中,最后统一打包就好了,而不需要多次打包,而且可以利用 `JSR` 发布 `deno` 包,这样之后不管是直接继续使用 `deno`,或者想改为使用 `node` 环境使用都能直接用。
#### 创建项目
##### 1. 初始化项目
`Tauri 2.0` 官方文档有提供使用 `deno` 进行初始化项目,即:
```bash
mkdir tauri2-deno-starter
cd tauri2-deno-starter
deno run -A npm:create-tauri-app
```
##### 2. 改造项目
按照官方指令创建出来的项目并不能直接利用 `deno workspace` 的功能来直接使用 `TypeScript` 代码在前端中使用。初始化出来的项目只是利用了 `deno install` 的能力安装依赖,实际上还是使用的 `package.json` 来管理依赖,而不是使用 `deno.json` 的方式管理依赖。
以创建前端项目为 `React + TypeScript` 为例:
(1) 在 `tauri2-deno-starter` 目录下创建 `deno.json` 管理全局依赖;
```json
{
"nodeModulesDir": "auto",
"workspace": [
"./app" // 假设创建的 tauri 项目为 app
],
"imports": {
"@tauri-apps/api": "npm:@tauri-apps/api@^2",
"@tauri-apps/cli": "npm:@tauri-apps/cli@^2"
}
}
```
设置 `nodeModulesDir` 为 `auto` 是为了 deno 安装依赖时会生成 node_modules 目录。
`imports` 中添加 `@tauri-apps/api` 和 `@tauri-apps/cli` 依赖方便开发插件和应用时都可以使用。
(2) 迁移 `app` 为使用 `deno` 管理依赖
删除各种 `tsconfig` 文件和 `package.json` 文件,添加 `deno.json` 文件。
`app/deno.json` 文件如下:
```json
{
"name": "@kingsword/app",
"version": "0.1.0",
"exports": {},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "npm:react@^19.0",
"jsxImportSourceTypes": "npm:@types/react@^19.0",
"lib": ["ES2020", "DOM", "DOM.Iterable"]
},
"tasks": {
"dev": "deno run -A npm:vite",
"build": "deno check src/main.ts && deno run -A npm:vite build",
"preview": "deno run -A npm:vite preview",
"tauri": "deno run -A npm:@tauri-apps/cli"
},
"imports": {
"@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.0",
"@vitejs/plugin-react": "npm:@vitejs/plugin-react",
"react": "npm:react",
"react-dom": "npm:react-dom",
"react-router-dom": "npm:react-router-dom",
"vite": "npm:vite@^6.0.3"
}
}
```
修改 `vite.config.ts`
```ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import deno from "@deno/vite-plugin";
import process from "node:process";
const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/
export default defineConfig(() => ({
plugins: [
deno(), //
react(),
],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent vite from obscuring rust errors
clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available
server: {
port: 1420,
strictPort: true,
host: host || false,
hmr: host
? {
protocol: "ws",
host,
port: 1421,
}
: undefined,
watch: {
// 3. tell vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**"],
},
},
}));
```
(3) 创建完整项目结构
添加 `plugins` 目录用于管理 `Tauri 2.0` 的插件,添加 `packages` 目录用于管理前端工具 `TypeScript` 包。完整项目结构如下:
```text
tauri2-deno-starter/
├── app/ # 应用主目录
│ ├── src/ # 前端源代码
│ └── src-tauri/ # Tauri相关配置和Rust代码
├── plugins/ # 自定义Tauri插件
├── packages/ # 自定义Deno TS包
├── deno.json # Deno配置文件
```
#### 开发 `deno` 包
##### 1. 创建 lib
```bash
cd packages
deno init [package_name] --lib
```
##### 2. 添加到 `workspace`
`tauri2-deno-starter/deno.json`
```json
{
"nodeModulesDir": "auto",
"workspace": [
"./app" // 假设创建的 tauri 项目为 app
"./packages/[package_name]"
],
"imports": {
"@tauri-apps/api": "npm:@tauri-apps/api@^2",
"@tauri-apps/cli": "npm:@tauri-apps/cli@^2"
}
}
```
##### 3. 发布 `deno` 包
如果需要发布 deno 包,提供给 deno 和 npm 用户使用,则可以查看 [JSR](https://jsr.io) 的文档,发布指令为:
```bash
deno publish
```
#### 开发 `Tauri 2.0` 插件
##### 1. 创建插件
```bash
cd plugins
deno run -A npm:@tauri-apps/cli plugin new [plugin_name]
```
##### 2. 添加到 `workspace`
在 `tauri-plugin-[plugins_name]` 和 `tauri-plugin-[plugins_name]/examples/tauri-app` 下都要添加 `deno.json` 文件。
`tauri-plugin-[plugins_name]/deno.json`:
```json
{
"name": "@kingsword/tauri-plugin-[plugins_name]-api",
"version": "0.1.0",
"exports": "./guest-js/index.ts"
}
```
`tauri-plugin-[plugins_name]/examples/tauri-app/deno.json` 则参考 `app/deno.json` 的设置,`examples/tauri-app` 中的修改也和 `app` 的修改一致,需要去掉 `tsconfig` 和 `package.json` 等。
`tauri2-deno-starter/deno.json`
```json
{
"nodeModulesDir": "auto",
"workspace": [
"./app" // 假设创建的 tauri 项目为 app
"./plugins/tauri-plugin-[plugins_name]",
"./plugins/tauri-plugin-[plugins_name]/examples/tauri-app",
],
"imports": {
"@tauri-apps/api": "npm:@tauri-apps/api@^2",
"@tauri-apps/cli": "npm:@tauri-apps/cli@^2"
}
}
```
##### 3. 发布插件
发布插件通常需要将 `guest-js` 目录下的 `TypeScript` 包发布到 `JSR` 以及 `rust` 包发布到 `crates`,deno 包发布已经在前面解释过,也可看文档,rust 包则使用如下命令:
```bash
cargo publish
# 不要通过构建它们来验证内容则:
cargo publish --no-verify
```
#### 运行项目
到此可以运行桌面端程序了:
```bash
deno task --filter 'app' tauri dev
```
#### 添加移动端支持
如果需要添加移动端的支持,还需要创建移动端项目,即
##### 1. 初始化移动端项目
```bash
cd app
deno run -A npm:@tauri-apps/cli android init
deno run -A npm:@tauri-apps/cli ios init
```
##### 2. 添加到 `workspace`
在 `android` 目录下添加 `deno.json`
```json
{
"name": "@kingsword/app-android",
"exports": {},
"tasks": {
"tauri": "deno run -A npm:@tauri-apps/cli"
}
}
```
在 `apple` 目录下添加 `deno.json`
```json
{
"name": "@kingsword/app-ios",
"exports": {},
"tasks": {
"tauri": "deno run -A npm:@tauri-apps/cli"
}
}
```
之后在 `tauri2-deno-starter/deno.json` 中添加即可:
```json
{
"nodeModulesDir": "auto",
"workspace": [
"./app" // 假设创建的 tauri 项目为 app
"./app/src-tauri/gen/android",
"./app/src-tauri/gen/ios"
],
"imports": {
"@tauri-apps/api": "npm:@tauri-apps/api@^2",
"@tauri-apps/cli": "npm:@tauri-apps/cli@^2"
}
}
```
##### 3. 运行移动端项目
```bash
# android
deno task --filter 'app' tauri android dev
# ios
deno task --filter 'app' tauri ios dev
```
以上就是开发一个使用 `deno workspace` 来管理 `Tauri 2.0` 应用依赖的过程。
#### 参考文档
- [Tauri 官方文档](https://v2.tauri.app/)
- [deno 官方文档](https://deno.com)
- [jsr publish](https://jsr.io/docs/publishing-packages)
- [cargo publish](https://doc.rust-lang.org/cargo/commands/cargo-publish.html)
- [tauri2-deno-starter](https://github.com/kingsword09/tauri2-deno-starter)
--- END OF POST ---
--- START OF POST ---
URL: https://blog.kingsword.tech/2025/03/30/react-native-repack-use-1/
Published At: 2025-03-30T00:00:00.000Z
# React Native 使用 Repack 创建项目(1)
#### 1. 创建项目
最近在使用 [Repack](https://github.com/callstack/repack) 创建 `react native` 项目。
由于 `react native` 项目都默认是 `yarn` 的支持更好,但是我想要使用 `pnpm` 初始化这个项目,即:
```bash
# repack 5.0.5
pnpm dlx @callstack/repack-init
```
#### 2. 安装依赖
创建完之后使用 `pnpm i` 安装以来之后,启动项目会失败,缺少了很多依赖,需要自行安装依赖才行。
缺少了以下依赖:
```bash
@react-native/gradle-plugin
@react-native/codegen
babel-plugin-syntax-hermes-parser
@babel/plugin-syntax-typescript
@react-native/babel-plugin-codegen
```
之后使用 `pnpm add -D` 安装这些依赖就行了。
#### 3. 运行
###### 1. Android
```bash
pnpm android
```
###### 2. iOS
```bash
# 安装 cocoapods 依赖
pnpm dlx pod-install
# 运行
pnpm ios
```
#### 4. QA
###### 1. 使用 `Android Studio` 打开 `android` 目录出现 `Cannot run program "node"` 异常时怎么解决?
(1)查询 `node` 指令位置
```bash
which node
```
(2)替换 `node_modules/@callstack/repack/android/build.gradle` 中的 `node` 指令
```diff
- commandLine("node", "--print", "require.resolve('react-native/package.json')")
# 比如 node 位置是 /usr/local/bin/node
+ commandLine("/usr/local/bin/node", "--print", "require.resolve('react-native/package.json')")
```
###### 2. 出现 `Missing ExternalProject for :` 异常?
替换 `android/settings.gradle` 中的如下内容:
```diff
- pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
+ pluginManagement { includeBuild(file('../node_modules/@react-native/gradle-plugin').toPath().toRealPath().toAbsolutePath().toString()) }
- includeBuild('../node_modules/@react-native/gradle-plugin')
+ includeBuild(file('../node_modules/@react-native/gradle-plugin').toPath().toRealPath().toAbsolutePath().toString())
```
#### 参考文档
- [React Native 官方文档](https://reactnative.dev/)
- [repack](https://re-pack.dev/)
- [Missing ExternalProject for :](https://github.com/react-native-community/cli/issues/1207#issuecomment-2232732690)
--- END OF POST ---
--- START OF POST ---
URL: https://blog.kingsword.tech/2025/03/28/tauri2-plugin-developerment-pitfall-record-2/
Published At: 2025-03-28T00:00:00.000Z
# Tauri 2.0 插件开发踩坑记录(2)
开发 `Tauri 2.0` 的插件时,可能会有异步返回数据的需求,这时候就需要使用到 [addpluginlistener](https://v2.tauri.app/reference/javascript/api/namespacecore/#addpluginlistener)。
假设要开发的插件为: [tauri-plugin-mobile-onbackpressed-listener](https://github.com/kingsword09/tauri-plugins-workspace/tree/main/packages/tauri-plugin-mobile-onbackpressed-listener)。
在 `TypeScript` 的接口定义中就可以以下面的方式进行定义:
`guest-js/index.ts`:
```ts
import {
addPluginListener,
invoke,
PluginListener,
} from "@tauri-apps/api/core";
export async function registerBackEvent(
goBack: () => void
): Promise {
await invoke("plugin:mobile-onbackpressed-listener|register_back_event");
return await addPluginListener(
"mobile-onbackpressed-listener",
"mobile-onbackpressed-goback",
(result) => {
goBack();
}
);
}
```
定义了一个插件 `command`:`register_back_event` 和一个监听 `event`:`mobile-onbackpressed-goback`。接下来就可以在各个平台进行实现相关代码了。
- 在 `Android` 端可以通过 `onBackPressedDispatcher` 和 `onBackInvokedDispatcher` 来监听设备的返回事件,`registerBackEvent` 这个事件处理时可以直接通过 `invoke.resolve()` 返回就可以了。
- 在 `iOS` 端,则需要做一些特殊处理,因为 `iOS` 本身没有返回监听相关的功能,需要具体实现,也可以参考 [MobileOnBackPressedListenerPlugin.swift](https://github.com/kingsword09/tauri-plugins-workspace/blob/main/packages/tauri-plugin-mobile-onbackpressed-listener/ios/Sources/MobileOnBackPressedListenerPlugin.swift) 此处代码来实现。
在这之后,你就可以通过监听到返回,在 `Android` 端和 `iOS` 端分别触发:`trigger("mobile-onbackpressed-goback", JSObject())` 来实现通知到前端代码来执行 `goBack` 方法。
当你以为一切都完成时,坑就出现了,你会发现 `trigger` 怎么都无法通知到前端,查阅官方文档也没有看到有相关的说明,而且官方似乎没有相关的示例可以查看。
后来是在看到第三方的插件时,通过对比才发现问题所在。需要将 `addPluginListener` 的 `command` 也添加到你的插件的 `command` 中,即
`tauri-plugin-mobile-onbackpressed-goback/build.rs`:
```rust
const COMMANDS: &[&str] = &[
"register_back_event",
"registerListener",
"remove_listener"
]
```
这样才能够实现 `trigger` 可以通知到前端。之后在使用插件时,也需要在 `app` 中的 `capabilities/*.json` 添加上相关的权限:
```json
"permissions": [
"mobile-onbackpressed-listener:allow-register-back-event",
"mobile-onbackpressed-listener:allow-registerListener",
"mobile-onbackpressed-listener:allow-remove-listener"
]
```
如此,这个插件就可以正常使用了。
#### 参考文档
- [Tauri 官方文档](https://v2.tauri.app/)
- [tauri-plugin-mobile-onbackpressed-listener](https://github.com/kingsword09/tauri-plugins-workspace/tree/main/packages/tauri-plugin-mobile-onbackpressed-listener)
--- END OF POST ---
--- START OF POST ---
URL: https://blog.kingsword.tech/2025/03/28/tauri2-plugin-developerment-pitfall-record-1/
Published At: 2025-03-28T00:00:00.000Z
# Tauri 2.0 插件开发踩坑记录(1)
最近在开发 `Tauri 2.0` 的插件的时候,遇到了一个问题,就是我在 `TypeScript` 中定义了一个对象参数用来传递给插件,在 `Android` 端会出问题,定义如下:
`guest-js/index.ts`
```ts
export interface Options {
privacyUrl: string;
isPrivacyDialogRequired: boolean;
}
```
在 `Android` 中定义如下:
```kotlin
@InvokeArg
class Options {
lateinit var privacyUrl: String
var isPrivacyDialogRequired: Boolean = true
}
```
按照这种方式定义之后,如果在调用这个插件的时候,`isPrivacyDialogRequired` 设置为 `false`,在 `Android` 的结果依然会是:`true`。
这个问题在 `[阿里巴巴Java开发手册](https://github.com/alibaba/p3c)` 中有提到:[【强制】POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。](https://github.com/alibaba/p3c/blob/6c59c8c36ecd8722c712d5685b8c3822c1c8b030/p3c-gitbook/%E7%BC%96%E7%A8%8B%E8%A7%84%E7%BA%A6/%E5%91%BD%E5%90%8D%E9%A3%8E%E6%A0%BC.md?plain=1#L22)
而 `Tauri 2.0` 默认使用的序列化框架 [jackson](https://github.com/FasterXML/jackson) 就会出现这个问题。两种方式解决这个问题:
1. 不使用 `is` 开头的属性来定义布尔类型变量;
2. 则是使用 `JsonProperty` 注解:
```kotlin
import com.fasterxml.jackson.annotation.JsonProperty
@InvokeArg
class Options {
lateinit var privacyUrl: String
@JsonProperty("isPrivacyDialogRequired")
var isPrivacyDialogRequired: Boolean = true
}
```
通过这样修改之后,就可以正常处理反序列化了。
#### 参考文档
- [Tauri 官方文档](https://v2.tauri.app/)
- [Jackson 官方文档](https://github.com/FasterXML/jackson)
- [阿里巴巴 Java 开发手册](https://github.com/alibaba/p3c)
--- END OF POST ---