FlutterとRustでAWS S3エクスプローラを作る(1/2)
自己紹介
初めての投稿になります。
普段はSIerでシステムエンジニアとして働いています。投稿する内容は趣味の範囲で色々と開発してみた内容になります。主にAWS、Flutterを行っています。
作成したアプリ
Flutterでのアプリ開発を数年行っていましたが、Rustの勉強もしたい..と思い始めました。
そこで、FlutterとRustを活用したアプリを
flutter_rust_bridgeを使って試験的に実装してみましたので利用方法を紹介してみます。
FlutterとRustを使ってアプリ開発してみたいという方のご参考になればと思います。
まずは今回の記事を通して作成するアプリのスクリーンショットを添付しておきます。
はじめに
下記の条件を満たしている方を対象としています。
満たしていない方は後述の参考をご確認いただき、インストールを実施してください。
今回は主にflutter_rust_bridge
の使い方についての紹介になります。
- Flutterインストール済み (3.7.7)
- Rustインストール済み (rustup: 1.25.2, cargo: 1.67.1)
参考
flutter_rust_bridgeの利用
まずはflutter_rust_bridge
を利用してFlutterとRustの連携を行ってみたいと思います。
実行はmacOS
を対象としています。
flutter_rust_bridgeインストール
まずはflutter_rust_bridge
をインストールしてプロジェクトを作成していきます。
cargo install flutter_rust_bridge_codegen
cargo install cargo-xcode
インストールには様々な方法があるみたいなので、他の方法は公式ドキュメントをご参照ください。
プロジェクト作成
公式にテンプレートがあるので、そこからclone
して触ってみることもできるのですが、
今回はFlutterプロジェクト作成してからflutter_rust_bridge
を利用する手順で行っていきます。
# プロジェクト作成
flutter create app
# flutter_rust_bridge用のCargoプロジェクト作成
cd app
cargo new --lib native
app
ディレクトリ配下にnative
ディレクトリが作成されたと思います。
native
ディレクトリ内は通常のCargoプロジェクトと同様の構成になっています。
native
├── src
│ └── lib.rs
├── Cargo.toml
Flutterプロジェクトに必要なパッケージをインストールしておきます
flutter pub add --dev ffigen && flutter pub add ffi
flutter pub add flutter_rust_bridge
flutter pub add -d build_runner
flutter pub add -d freezed
flutter pub add freezed_annotation
flutter pub add meta
Cargoプロジェクト設定
native/Cargo.toml
ファイルに下記の[lib]
と[dependencies]
部分を追記します
[package]
edition = "2021"
name = "native"
version = "0.1.0"
[lib]
crate-type = ["staticlib", "cdylib"]
[dependencies]
flutter_rust_bridge = "1"
macOS、iOSで利用するXcodeプロジェクトファイルを作成します。
cd native
cargo-xcode
native
ディレクトリ配下にnative.xcodeproj
が作成されたことを確認します。
こちらはmacOSまたはiOSのXcodeプロジェクトの設定で利用します。
Xcodeプロジェクト設定
macOSのXcodeプロジェクトに作成したCargoプロジェクトを設定していきます。
ここの設定はios/Runner.xcworkspace
でも同様となります。
-
Xcodeプロジェクトを開く
open macos/Runner.xcworkspace
-
プロジェクト追加
-
Runner
ディレクトリ上で右クリックし、「Add File to "Runner"...」を選択 -
先程作成した「
native/native.xcodeproj
」を選択
※「Copy item if needed」にチェックがついていないこと
-
-
プロジェクトにsoファイルを追加
- 「Runner」プロジェクトから Build Phases タブを選択
-
Target Dependencies の「+」ボタンを押下し、
native-staticlib
を選択して追加- 公式ドキュメントではMacOSの場合は
$crate-cdylib
を選択とありますが、そちらでは正常にアプリ起動できなかったため、staticlib
を選択しています。
- 公式ドキュメントではMacOSの場合は
-
Build Phases タブ内の Link Binary With Librariesの「+」ボタンを押下し、
libnative_static.a
を選択して追加- こちらも公式ドキュメントではMacOSの場合は
$crate.dylib
を選択とありますが、そちらでは正常にアプリ起動できなかったため、static.a
を選択しています。
- こちらも公式ドキュメントではMacOSの場合は
サンプルコード実装
まずはサンプルコードを記述し、コードジェネレータを利用してDart
、Rust
のコードとiOS、macOS用のBridgeHeader
ファイルを出力します。
新たにnative/src/api.rs
ファイルを作成して関数を一つ実装します。
pub fn greet() -> String {
"Hello from Rust!".into()
}
api.rs
を読み込めるようlib.rs
ファイルも合わせて更新します。
mod api;
コード生成
flutter_rust_bridge_codegen
コマンドを利用してDart
とRust
のBridgeファイルを生成します。
Dart
ファイルの出力先はブリッジファイルをまとめるためbridge
ディレクトリを作成し、そこに出力するようにしています。
(--dart-decl-output
オプションがないと上手くいかなかったです..やり方が悪かったのか。。)
mkdir lib/bridge
flutter_rust_bridge_codegen \
-r native/src/api.rs \
-c ios/Runner/bridge_generated.h \
-e macos/Runner/ \
--dart-output lib/bridge/bridge_generated.dart \
--dart-decl-output lib/bridge/bridge_definitions.dart
...
Formatted 2 files (2 changed) in 0.10 seconds.
2023/03/19 22:52:42 [INFO] Success!
2023/03/19 22:52:42 [INFO] Now go and use it :)
Success!
と表示されれば、生成完了です。Dartのファイルが2つ、Rustのファイルが2つ生成されていると思います。
app
├── lib
│ └── bridge
│ ├── bridge_definitions.dart
│ └── bridge_generated.dart
├── native
│ └── src
│ ├── bridge_generated.io.rs
│ └── bridge_generated.rs
最後にDartでジェネレートファイルを呼び出すためにlib/bridge/ffi.dart
を生成します。
// This file initializes the dynamic library and connects it with the stub
// generated by flutter_rust_bridge_codegen.
import 'dart:ffi';
import 'dart:io' as io;
import 'bridge_definitions.dart';
import 'bridge_generated.dart';
export 'bridge_definitions.dart';
// Re-export the bridge so it is only necessary to import this file.
export 'bridge_generated.dart';
const _base = 'native';
// On MacOS, the dynamic library is not bundled with the binary,
// but rather directly **linked** against the binary.
final _dylib = io.Platform.isWindows ? '$_base.dll' : 'lib$_base.so';
final Native api = NativeImpl(io.Platform.isIOS || io.Platform.isMacOS
? DynamicLibrary.executable()
: DynamicLibrary.open(_dylib));
こちらのファイルは公式からのコピペです。const _base = 'native';
の部分のみ、Cargoで生成したプロジェクト名に設定するようにしてください。
macOSのBridgeHeader設定
上記のコード生成時にmacOS用のbridge_generated.h
が生成されていますので、プロジェクトの設定を行います。
Xcodeでmacos/Runner.xcworkspace
を開き、「Runner」ディレクトリ上で右クリックし、「 Add File to "Runner"... 」を選択します。同じディレクトリ内にbridge_generated.h
がありますので選択して追加します。
AppDelegate.swift
にdummy_method_to_enforce_bundling()
を追加します。
import Cocoa
import FlutterMacOS
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
dummy_method_to_enforce_bundling()
return true
}
}
iOSの場合
iOSの場合はRunner-Bridging-Header.h
がありますので、そちらにimportの追加を行います。
#import "bridge_generated.h"
その後、macOS同様AppDelegate.swift
にdummy_method_to_enforce_bundling()
を追加します。
Flutterから呼び出し
ここまでくれば、あとはFlutter側から呼び出すだけです。
デフォルトで生成されているカウンターアプリを少し修正してRustのgreet
メソッドを呼び出して結果を出力する部分を実装していきます。
class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FutureBuilder<String>(
future: api.greet(),
builder: (context, shapshot) {
return Text(
shapshot.data ?? 'No Data',
);
},
),
],
),
),
);
}
}
flutter pub get
flutter run -d macos
Rustのgreet
関数を呼び出し、結果を出力するだけです。api.greet()
はFuture<String>
となるので、FutureBuilder
を利用して、結果をTextで表示します。上手く呼び出しができない場合はExceptionが発生するか「No Data
」が表示されると思います。「Hello from Rust!
」が表示されればOKです。
最後に
ここまでお読みいただきましてありがとうございます。
以上でflutter_rust_bridg
のセットアップとサンプルの実行は終わりになります。
次は実際にRustのAWS SDKを使ってみてS3へのアクセスを実装していきたいと思います。
次回もよろしくお願いします。
あまり更新頻度は高くないですが、GitHubにいくつかコードをあげているのでお時間ある方は少し覗いていただけると有り難いです。
Discussion