使用rinf进行Rust与Flutter的集成与通信

使用rinf进行Rust与Flutter的集成与通信

前言

Flutter和Rust之间的跨语言调用做着有一段时间了,从去年开始尝试手动写外部函数接口(FFI),把动态库通过交叉编译手动放进文件夹到现在找一些便利的lib直接用起来,起初用的是FRB(flutter_rust_bridge),但FRB这东西说实话有点繁重,直接生成一堆文件,我不太喜欢

这几天看到了rinf,觉得用的挺不错的,看了看关于rinf的文章不是很多,于是就稍微写写,如何使用还是建议参考官方文档

安装

安装非常方便,当然了,你得先装好Flutter和Rust

添加依赖

找到你的 Flutter 项目根目录(下面简称根目录)运行下面这个命令添加依赖,或者你也可以手动安装

1
flutter pub add rinf

安装之后可以使用rinf --help看看详细帮助

生成模板文件

使用这个命令来生成模板文件,模板代码也是文档的一部分,推荐进行参考

1
rinf template

Rust相关的代码编写主要是在 native/hub/src

使用

编写ProtoBuf

不会编写请先参考文档

在messages文件夹下新建foo.proto(名称随意),使用proto3进行编写即可

下面这段代码可以进行参考

其中[RINF:DART-SIGNAL](从Dart向Rust传递的信息)和[RINF:RUST-SIGNAL](从Rust向Dart发送的信息)并不是无意义的,rinf会根据这个来生成文件

1
2
3
4
5
6
7
8
9
10
11
12
13
syntax = "proto3";
package foo;

// [RINF:DART-SIGNAL]
message DataInput {
string username = 1;
string password = 2;
};

// [RINF:RUST-SIGNAL]
message DataOutput {
string data = 1;
}

如果你需要一个数组或者可选参数

1
2
3
4
message Foo {
repeated string vec = 1;
optional string opt = 2;
}

如果需要引用其他proto文件中的类型

1
2
3
4
5
6
import "path/to/foo.proto";

message Bar {
foo.Foo data = 1;
}

写完之后,根目录运行rinf message就可以看到文件生成了

Rust侧的编写

首先可以在hub/src下新建一个foo.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
// foo.rs

use crate::messages::foo::{DataInput, DataOutput};

pub async fn handle_data() {
let mut rev = DataInput::get_dart_signal_receiver();
while let Some(dart_signal) = rev.recv().await {
// 处理接受来的信息
let output: DataOutput = foo(dart_signal.message);
// 发送
output.send_signal_to_dart(None);
}
}

然后还需要在主函数里添加一行spawn,这样rust侧的编写就完成了

1
2
3
4
5
// ...
fn main {
// ...
tokio::spawn(handle_data());
}

Flutter侧的编写

Flutter侧的代码默认会生成在lib/messages中,只需要关心generated.dart里的内容即可

需要先在主函数添加初始化Rust,并且添加生命周期确保关闭

在生成模板时,会帮你自动添加初始化的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void main() async {
//...
await initializeRust(assignRustSignal);
//...
}

class MyAppState extends State<MyApp> {
final _appLifecycleListener = AppLifecycleListener(
onExitRequested: () {
finalizeRust();
return AppExitResponse.exit;
},
);

@override
void dispose() {
super.dispose();
_appLifecycleListener.dispose();
}
}

然后就可以进行调用了!下面是一个简单的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class FooPageState extends State<FooPage> {
void update(DataOutput data) => ...

late StreamSubscription<RustSignal<DataOutput>> _streamDataOutput;

@override
void initState(){
super.initState();
_streamDataOutput = DataOutput.rustSignalStream.listen((signal) => update(signal.message));
}


@override
void dispose(){
super.dispose();
_streamDataOutput.cancel();
}

@override
Widget build(BuildContext context) {
return Button(onTap: () => DataInput(
username: "Foo",
password: "Bar",
).sendSignalToRust()
);
}
}

更推荐使用 StreamBuilder,代码更加清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class FooPageState extends State<FooPage> {
@override
void initState(){
super.initState();
DataInput(
username: "Foo",
password: "Bar",
).sendSignalToRust()
}

@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: DataOutput.rustSignalStream,
builder: (context, snapshot){
var signal = snapshot.data;
if (signal == null){
return ...
}

return Button(onTap: () => DataInput(
username: "Foo",
password: "Bar",
).sendSignalToRust()
);
}
);
}
}

使用rinf进行Rust与Flutter的集成与通信
http://h2sxxa.github.io/2024/02/10/how_to_use_rinf/
作者
H2Sxxa
发布于
2024年2月10日
许可协议