Upgrade to Pro — share decks privately, control downloads, hide ads and more …

オフライン対応!Flutterアプリに全文検索エンジンを実装する @FlutterKaig...

Avatar for JaiChangPark JaiChangPark
November 13, 2025

オフライン対応!Flutterアプリに全文検索エンジンを実装する @FlutterKaigi2025

オフライン対応!Flutterアプリに全文検索エンジンを実装する - Dreamwalker
# FlutterKaigi2025 #FlutterKaigi
@jaichangpark

Avatar for JaiChangPark

JaiChangPark

November 13, 2025
Tweet

More Decks by JaiChangPark

Other Decks in Programming

Transcript

  1. • 多くのAIアプリはネットワークを利用してサーバーのAPIを呼び出して動作する。 ◦ ネットワークなしでは利用困難。 • オンデバイス AI, On-Device AI ◦

    ネットワークが利用できない環境。 ◦ オフラインでもAIを活用可能であることが重要。 • LLMの制約 ◦ ただし、LLMにはKnowledge Cutoffがある。 * オンデバイスAI : On-Device AI * LLM: Large Language Model * オフライン: Offline Problem & Motivation 4
  2. • 検索拡張生成、 RAG (Retrieval-Augmented Generation) ◦ Knowledge Cutoff を解消 ◦

    知識追加 ▪ 自分専用, カスタム, 社内データ など ◦ ネットワーク依存 → オフライン対応が必要 • 知識追加方法 ◦ プロンプトにコンテキストを提供 ▪ ベクトルデータベース(Vector Database)を活用 • 埋め込み(Embedding) + 類似度検索(Similarity Search) ▪ ハイブリッド検索 ▪ Web検索 Problem & Motivation 8
  3. 9

  4. 11

  5. • ChromaDB ◦ https://www.trychroma.com/home • Milvus ◦ https://milvus.io/ja • Qdrant

    ◦ https://qdrant.tech/ • Pinecone ◦ https://www.pinecone.io/ サーバーを構築または利用する場合、オンライン(ネットワーク)接続が必要。 ベクトルデータベース • DuckDB ◦ https://duckdb.org/ • ElasticSearch ◦ https://www.elastic.co/jp/elasticsearch • OpenSearch ◦ https://opensearch.org/ 12
  6. EmbeddingGemmaやQwenEmbeddingを利用可能 • EmbeddingGemma ◦ 300M parameter ◦ Output embedding dimension

    size of 768, with smaller options available (512, 256, or 128) via Matryoshka Representation Learning (MRL) ◦ https://huggingface.co/google/embeddinggemma-300m • QwenEmbedding ◦ 0.6B(600M), 4B, 8B parameter ◦ dimension size: 1024, 2560, 4096 ◦ https://huggingface.co/Qwen/Qwen3-Embedding-0.6B テキスト埋め込み 13
  7. 全文検索(Full Text Search, FTS)とは、 文書全体を対象にテキストを検索する仕組みである。 単なるキーワード一致ではなく、 文章の意味や形態素 を解析して、より適切な結果を返す。 代表的な全文検索エンジンには以下がある: •

    Elasticsearch:オープンソースで高性能な全文検索エンジン。 • OpenSearch:ElasticsearchをベースにAmazonが提供するオープンソース版。 全文検索, Full Text Search 15
  8. 条件 • オフライン状態 要件 • 既存ではネットワーク必須だった機能を、 すべてオフライン環境でも動作可能にする必要がある。 現状 • ✅

    スマートフォン上でLLMを動作させることは可能。 • ✅ スマートフォン上でテキスト埋め込み、類似度検索は可能。 • 📍 知識検索の性能向上方法が必要。 • 📍 Flutterアプリでオフライン状態でも使用可能な全文検索エンジンが必要。 17
  9. • CJK 検索が必要。 • SQLite FTS5 → 英語には強力だが、CJK対応は限定的。 次のステップ 1.

    CJK対応可能なトークナイザーの調査。 2. 関連するFlutterパッケージが存在するかを調査。 SQLite FTS5 Extension 24 SQLite FTS5
  10. 形態素解析器 (トークナイザ) 一般的に形態素解析器(トークナイザー)を活用して改善を行 う • jieba ◦ 中国語 ◦ https://github.com/fxsjy/jieba

    • mecab - ipadic ◦ 日本語 ◦ https://github.com/taku910/mecab • mecab - ko & Kiwi ◦ 韓国語 25 @source: https://taku910.github.io/mecab/
  11. • https://github.com/wangfenjin/simple ◦ SQLite3 FTS5 × 中国語トークナイザー対応。 ◦ jieba, PinYin

    • https://github.com/thino-rma/fts5_mecab ◦ SQLite3 FTS5 x mecab ◦ Flutterパッケージではない。 • https://github.com/SageMik/sqlite3_simple ◦ Flutterパッケージとして提供されているもの。 ◦ simple プロジェクトをFlutterで利用可能にしたパッケージ。 ◦ 中国語には対応しているが、日本語・韓国語のトークナイザーは未対応。 SQLite FTS5 Extension 26 SQLite FTS5
  12. • Tantivyは、Rustで開発された高速全文検索エンジンライブラリであり、Apache Luceneから着想 を得ている。 • 組み込み型ライブラリであり、ElasticsearchやOpenSearchのような外部サーバーを必要とせず、 アプリケーション内で直接利用可能である。 特徴 • 高速なインデックス作成と検索:大規模データでも高速にインデックスを構築可能(例:英語版

    Wikipediaを数分で処理)。 • BM25スコアリング:Luceneと同様のBM25アルゴリズム採用。 • マルチスレッド対応:インデックス作成・検索を並列処理可能。 • 軽量で高速起動:10ms未満で起動、CLIツールや小規模サーバーに適合。 Tantivy:Rust製の高速全文検索エンジンライブラリ Tantivy 27 Tantivy
  13. • メモリ安全性を保証(ガベージコレクター不 要)。 • 高速なパフォーマンス。 • 並行処理(Concurrency)に強い。 • C/C++との高い互換性。 •

    クロスプラットフォーム対応が容易。 https://rust-lang.org 安全で高速なシステムプログラミング言語 🦀 Rust 28 以前 Rust 難しい。 現在 • AIを使って一緒にできる。 with Gemini CLI, Claude • AIと勉強できる。 ⇒ 難しくない。
  14. 31 基本から 基本が大切。Rust + FFIの基本から始めてみよう。 [package] name = "flutter_rust_lib" version

    = "0.1.0" edition = "2025" [dependencies] # Cの動的ライブラリを生成するための追加 [lib] crate-type = ["cdylib"] Rust Native
  15. 32 基本 Rust + FFIの基本から始めてみよう。 [package] name = "flutter_rust_lib" version

    = "0.1.0" edition = "2025" [dependencies] # Cの動的ライブラリを生成するための追加 [lib] crate-type = ["cdylib"] FFI(Foreign Function Interface)は、 Rustで書いたコードを他の言語から呼び出すための 仕組みである。 C言語互換を持つFFI用の動的ライ ブラリを生成する設定である。 Rust Native
  16. 33 基本 Rust + FFIの基本から始めてみよう。 Rust Native /// FFI: シンプルで高速な同期関数

    /// 2つの整数を足して返します。 #[unsafe(no_mangle)] pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }
  17. 34 基本 Rust + FFIの基本から始めてみましょう。 Rust Native /// FFI: シンプルで高速な同期関数

    /// 2つの整数を足して返します。 #[unsafe(no_mangle)] pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b } • no_mangle: この関数の名前は他の言語から参照できるよう、絶対に変更しないこと。 • unsafe: この関数は外部と通信するため、Rustの安全規則が適用されない場合がある ことを理解し、責任を持って扱う。
  18. 35 基本 Rust + FFIの基本から始めてみましょう。 Rust Native /// FFI: シンプルで高速な同期関数

    /// 2つの整数を足して返します。 #[unsafe(no_mangle)] pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b } • pub: Rustの関数・構造体は基本private。 • extern "C" : C ABIに従ってコンパイル。
  19. 36 基本 Rust + FFIの基本から始めてみよう。 Rust Native #[unsafe(no_mangle)] pub extern

    "C" fn heavy_computation(name: *const c_char) -> *mut c_char { let name_str = unsafe { // code .. }; // CPUを多く使用する作業のシミュレーション thread::sleep(Duration::from_secs(3)); // 3秒待機 let result = format!("Hello, {}. Welcome from Rust!", name_str); // RustのStringをC文字列に変換してポインタを返す // .into_raw()はRustがメモリを解放しないように所有権を渡します。 CString::new(result).unwrap().into_raw() }
  20. 39 基本 dart ffi class RustFFI { static final _lib

    = _loadLibrary(); static DynamicLibrary _loadLibrary() { if (Platform.isMacOS) { return DynamicLibrary.open('libflutter_rust_lib.dylib'); } throw UnsupportedError('Unsupported platform'); } }
  21. 40 基本 dart ffi static final Pointer<Utf8> Function(Pointer<Utf8>) _heavyComputationSync =

    _lib .lookup<NativeFunction<Pointer<Utf8> Function(Pointer<Utf8>)>>( 'heavy_computation', ) .asFunction(); #[unsafe(no_mangle)] pub extern "C" fn heavy_computation(name: *const c_char) -> *mut c_char {
  22. 41 基本 dart ffi String _heavyComputationIsolate(String name) { final nameC

    = name.toNativeUtf8(); final resultC = RustFFI._heavyComputationSync(nameC); final result = resultC.toDartString(); // code .. return result; } static Future<String> heavyComputationAsync(String name) async { return compute(_heavyComputationIsolate, name); } ffi Lookup
  23. 42 基本 Flutter Future<String>? _heavyTaskFuture; void _runHeavyTask() { setState(() {

    _heavyTaskFuture = RustFFI.heavyComputationAsync("Flutter"); }); } FutureBuilder<String>( future: _heavyTaskFuture, builder: (context, snapshot) { if (_heavyTaskFuture == null) { // code.. } if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator.adaptive(); } if (snapshot.hasError) { return Text('${snapshot.error}'); } if (snapshot.hasData) { return Text('Result:\n${snapshot.data}'); } return const SizedBox.shrink(); }, ),
  24. Native API 設計 46 • 初期化処理 → 関数名: tantivy_init ◦

    Index 設定 ◦ Schema 設定 ◦ reader 設定 ◦ writer 設定 • ドキュメント追加 → 関数名: tantivy_add_doc ◦ writer.add_documentを使用してインデックスにドキュメントを追加可能 • ドキュメント検索 → 関数名: tantivy_search ◦ ユーザーの検索クエリを受け取り検索を実行 ◦ 検索結果を返却 ◦ 検索結果は n 件を JSON 文字列として処理 Flutter Tantivy 開発
  25. ネイティブコード開発 47 [lib] crate-type = ["cdylib", "staticlib"] [dependencies] tantivy =

    "0.25.0" once_cell = "1.19" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } thiserror = "2.0.17" [build-dependencies] cbindgen = "0.29.2" Rust コードを基に、C / C++ のヘッダーファイルを自動生成するツー ル コアになる Tantivy ライブラリ Flutter Tantivy 開発
  26. 48 #[no_mangle] pub extern "C" fn tantivy_init(dir_path: *const c_char) ->

    c_int { } #[no_mangle] pub extern "C" fn tantivy_add_doc(text_ptr: *const c_char) -> c_int { } #[no_mangle] pub extern "C" fn tantivy_search(query_ptr: *const c_char, top_k: c_int) -> *mut c_char { } Flutter Tantivy 開発 ネイティブコード開発
  27. 49 use std::env; use std::path::PathBuf; fn main() { let crate_dir

    = env::var("CARGO_MANIFEST_DIR").unwrap(); let output_file = PathBuf::from(&crate_dir).join("binding.h"); cbindgen::Builder::new() .with_crate(crate_dir) .with_language(cbindgen::Language::C) .generate() .expect("Unable to generate bindings") .write_to_file(output_file); } build.rs ファイルを作成後、 cargo build --release を実行すると、 binding.h ファイルが生成される。 Flutter Tantivy 開発 ネイティブコード開発
  28. 50 #include <stdarg.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> char

    *tantivy_last_error(void); void tantivy_free_str(char *s); int tantivy_init(const char *dir_path); int tantivy_add_doc(const char *text_ptr); char *tantivy_search(const char *query_ptr, int top_k); binding.h ビルド時に自動生成されたファイルを利用 して、その後 ffigenを活用し、コード自動 生成に利用 Flutter Tantivy 開発 ネイティブコード開発
  29. ライブラリ ビルド 51 #!/bin/bash set -e cd rust_tantivy echo "🔧

    Building for macOS..." cargo build --release echo "🔧 Building for Android..." if command -v cargo-ndk &> /dev/null; then cargo ndk -t arm64-v8a -t armeabi-v7a -o ../android/app/src/main/jniLibs build --release else echo "⚠ Skipping Android (cargo-ndk not installed)" fi make または sh スクリプトを使用し て、プラットフォーム別・アーキテクチャ 別のビルドファイルを生成 Android の場合、ビルドされた出力物 は以下のディレクトリに配置 Flutter Tantivy 開発
  30. ffigenを使用、 FFI コード自動生成 53 dev_dependencies: flutter_test: sdk: flutter # The

    "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^6.0.0 ffigen: ^19.1.0 pubspec.yaml ファイル内の dev_dependencies セクションに ffigen を追加 Flutter Tantivy 開発
  31. 54 name: SearchFFI description: Bindings for search_ffi output: 'lib/ffi.dart' headers:

    entry-points: - 'rust_tantivy/binding.h' comments: style: any length: full preamble: | // ignore_for_file: camel_case_types, non_constant_identifier_names, unused_import コード生成の成果物の 場所およびファイル名 コード生成の基礎となる ヘッダーファイル Flutter Tantivy 開発 ffigenを使用、 FFI コード自動生成
  32. 55 > dart run ffigen --config ffigen.yaml Flutter Tantivy 開発

    ffigenを使用、 FFI コード自動生成
  33. ffi wrapper class 開発 56 class TantivySearch { late final

    SearchFFI _bindings; late final ffi.DynamicLibrary _dylib; TantivySearch() { _dylib = _loadLibrary(); _bindings = SearchFFI(_dylib); } } Flutter Tantivy 開発 自動生成されたFFIコードをそのまま使用するのではなく、 ラッパークラスを作成して利用す る。
  34. ffi wrapper class 開発 Flutter Tantivy 開発 57 List<Map<String, dynamic>>

    search(String query, {int topK = 10}) { final queryPtr = query.toNativeUtf8(); try { final resultPtr = _bindings.tantivy_search(queryPtr.cast<ffi.Char>(), topK); try { final jsonStr = resultPtr.cast<Utf8>().toDartString(); final results = jsonDecode(jsonStr) as List; return results.map((e) => e as Map<String, dynamic>).toList(); } finally { _bindings.tantivy_free_str(resultPtr); } } finally { malloc.free(queryPtr); } } FFI メソッド FFI Wrapper Method
  35. • https://github.com/yiv/full_search ◦ Tantivyをベースにした既存のパッケージがある。 ◦ このパッケージでは中国語のトークナイザーに対応している。 ◦ 保守性に課題があり、 ▪ 最新のFlutterバージョンを使用した場合、

    ビルドエラーが発生する問題が確認された。 • 日本語・韓国語向けのトークナイザーに対応した既存のパッケージは見つからな かった。 ◦ そのため、自分で実装することに。 Flutter パッケージはなかったのか? Flutter Tantivy 60
  36. • Rustで作られた形態素解析ライブラリ。 ◦ 本プロジェクトは kuromoji-rs からフォークされたもの。 ◦ https://github.com/lindera/lindera • Linderaは、インストールが容易で、さまざまなRustアプリケーション向けに簡潔なAPIを提

    供するライブラリの構築を目指している。 • lindera-sqlite, lindera-python, lindera-tantivy など。 • Flutterで利用可能なパッケージは見つからなかった。 A multilingual morphological analysis library. Lindera 61
  37. • Lindera-Tantivyは、Rust製の全文検索エンジンTantivyを他のアプリケーションで利用できるよう にしたライブラリである。 • 特徴 ◦ オフライン全文検索対応:ネットワーク接続がなくても高速に検索可能。 ◦ 形態素解析対応:日本語などの複雑な言語のテキストも正確に分割・検索できる。 ◦

    Rust の高性能を活かす:Tantivy の高速検索性能を Flutter から利用可能。 ◦ クロスプラットフォーム対応:モバイルアプリやデスクトップアプリでも使用可能。 • Flutterで利用可能なパッケージは見つからなかった。 Lindera tokenizer for Tantivy. Lindera-Tantivy 62
  38. Flutter Rust Bridge 64 • シンプル (Simplicity) ◦ 簡単なセットアップ:1つのコマンドで既存のプロジェクトに導入できる。 ◦

    自然なコーディング:DartとRustで普段通りにコードを書くだけで、複雑な型や非同期処理も簡単に関数連携が可能 である。 • 強力な機能 (Powerfulness) ◦ 多様な型をサポート:シリアライズ不可能な型や&mut参照など、Rustの高度な型をそのまま利用できる。 ◦ 双方向・非同期通信:DartからRustを呼び出すだけでなく、RustからDartの関数を呼び出すことも可能である。ま た、async処理にも対応している。 ◦ クロスプラットフォーム:iOS, Android, Windows, macOS, Linux, Webの主要プラットフォームをサポートしている。 • 高い信頼性 (Reliability) ◦ Flutter Favorite パッケージです。 ◦ 堅牢なテスト: CIによるメモリ安全性や各プラットフォームでの動作が保証されています。 https://github.com/fzyzcjy/flutter_rust_bridge
  39. Flutter Rust Bridge 65 • Rust のセットアップ(ダウンロードとインストール) ◦ flutter_rust_bridge_codegen →

    Code generator ◦ > cargo install flutter_rust_bridge_codegen • プロジェクトの作成( Flutter + Rust) ◦ > flutter_rust_bridge_codegen create my_app プロジェクトを作成する
  40. Flutter Rust Bridge 66 プロジェクト構造 Rustネイティブコード用フォルダ このパッケージはFlutterの FFI(Foreign Function Interface)プラグインであ

    る。 Flutterが各プラットフォーム (Android、iOSなど)向けに ビルドされる際、ネイティブ コード(この場合はRust)も 同時にビルドできるように統 合する仕組みを提供する。 flutter_rust_bridge_codegen というコード生成ツールが使 用する設定ファイルである。
  41. Flutter Rust Bridge 67 プロジェクト構造 • flutter_rust_bridge.yaml ◦ rust_input: crate::api

    ▪ Rustコードのうち、どの部分(この場合はapiモジュール)をDartに公開するかを指定す る。 ◦ rust_root: rust/ ▪ Cargo.tomlが存在するRustプロジェクトの場所を指定する。 ◦ dart_output: lib/src/rust ▪ 生成されたDartコードをどこに保存するかを指定する。
  42. Flutter Rust Bridge 68 Rust Native Code #[flutter_rust_bridge::frb(sync)] pub fn

    greet(name: String) -> String { format!("Hello, {name}!") } マクロ (macro):Rust 関数を Dart から同期呼び出し 可能なコードを生成。
  43. Flutter Rust Bridge 69 Rust Native Code pub fn async_greet(name:

    String) -> String { format!("Hello, {name}!") } > flutter_rust_bridge_codegen generate
  44. Flutter Rust Bridge 70 Rust Native Code String greet({required String

    name}) => RustLib.instance.api.crateApiSimpleGreet(name: name); Future<String> asyncGreet({required String name}) => RustLib.instance.api.crateApiSimpleAsyncGreet(name: name); 自動生成され た FFI コード 自動生成される FFI コードのラッパークラス。 各ネイティブ関数を使いやすくする。
  45. Flutter Rust Bridge 71 ビルドメカニズム • flutter runを実行すると、各プラットフォームのコード内にはライブラリファイルが存在しないのに、 なぜRustコードがビルドされ、実行できるのだろうか? •

    例えば、Androidではsrc/main/jniLibsにライブラリが必要だが、 存在しなくても正常に動作する。 • それはなぜ? ◦ どのような仕組みで可能になっているのでしょうか?
  46. Flutter Rust Bridge 73 def cargoOutputDir = "${project.buildDir}/jniLibs/${buildType}"; def jniLibs

    = project.android.sourceSets.maybeCreate(buildType).jniLibs; jniLibs.srcDir(new File(cargoOutputDir)) def task = project.tasks.create(taskName, CargoKitBuildTask.class) { buildMode = variant.buildType.name buildDir = cargoBuildDir outputDir = cargoOutputDir ndkVersion = plugin.project.android.ndkVersion sdkDirectory = plugin.project.android.sdkDirectory minSdkVersion = plugin.project.android.defaultConfig.minSdkVersion.apiLevel as int compileSdkVersion = plugin.project.android.compileSdkVersion.substring(8) as int targetPlatforms = platforms pluginFile = CargoKitPlugin.file } ビルドメカニズム `build/jniLibs/debug` 폴더도 네이티브 라이브러리 소스 폴더로 추가해서 사용해!" • Rustビルドの成果物を保存する一時フォルダ のパスを指定する。 • cargoOutputDirフォルダはjniLibsフォルダと同 様に扱われる。 build/jniLibs/debugフォルダもネイティブライブラリのソースフォルダと して追加して使用する。
  47. Flutter Rust Bridge 74 abstract class CargoKitBuildTask extends DefaultTask {

    @TaskAction def build() { def executableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "run_build_tool.cmd" : "run_build_tool.sh" project.exec { executable path args "build-gradle" environment "CARGOKIT_ROOT_PROJECT_DIR", rootProjectDir // code ... environment "CARGOKIT_JAVA_HOME", System.properties['java.home'] } } } ビルドメカニズム
  48. Flutter Lindera Tantivy 76 Crates 追加 [lib] crate-type = ["cdylib",

    "staticlib"] [dependencies] flutter_rust_bridge = "=2.11.1" tantivy = "0.25.0" lindera = "1.4.1" lindera-tantivy = { version = "1.1.1", features = [ "embedded-ipadic", "embedded-unidic", "embedded-ko-dic", "embedded-cc-cedict" ] } serde_json = "1.0"
  49. Flutter Lindera Tantivy 77 Rust ネイティブ API の設計 • ローカルディスクに保存

    ◦ 再実行時にデータが初期化されないように。 ◦ 関数名 ▪ initialize_search_index_with_path • CRUD 操作 が可能であること。 ◦ 文書の追加 ▪ 関数名: add_document , add_documents ◦ 検索 ▪ 関数名: search_documents ◦ 更新 ▪ 関数名: update_document ◦ 削除 ▪ 関数名: delete_document, delete_documents
  50. Flutter Lindera Tantivy 78 Rust Native Code #[derive(Clone, Debug)] pub

    enum DictionaryType { Korean, // ko-dic JapaneseIpadic, // ipadic JapaneseUnidic, // unidic Chinese, // cc-cedict } impl DictionaryType { fn to_embedded_path(&self) -> &'static str { match self { DictionaryType::Korean => "embedded://ko-dic", DictionaryType::JapaneseIpadic => "embedded://ipadic", DictionaryType::JapaneseUnidic => "embedded://unidic", DictionaryType::Chinese => "embedded://cc-cedict", } } fn to_tokenizer_name(&self) -> &'static str { match self { DictionaryType::Korean => "lang_ko", DictionaryType::JapaneseIpadic => "lang_ja_ipadic", DictionaryType::JapaneseUnidic => "lang_ja_unidic", DictionaryType::Chinese => "lang_zh", } } }
  51. Flutter Lindera Tantivy 79 Rust Native Code #[flutter_rust_bridge::frb(sync)] pub fn

    initialize_search_index_with_path(dictionary_type: DictionaryType, index_path: String) -> Result<String, String> { let tokenizer_name = dictionary_type.to_tokenizer_name(); index .tokenizers() .register("ngram_tokenizer", NgramTokenizer::new(2, 3, false).unwrap()); // Tokenizer with selected dictionary let mode = Mode::Normal; let dictionary = load_dictionary(dictionary_type.to_embedded_path()).map_err(|e| e.to_string())?; let user_dictionary = None; let segmenter = Segmenter::new(mode, dictionary, user_dictionary); let tokenizer = LinderaTokenizer::from_segmenter(segmenter); // register Lindera tokenizer index.tokenizers().register(tokenizer_name, tokenizer); }  初期化する。
  52. Flutter Lindera Tantivy 80 #[flutter_rust_bridge::frb(sync)] pub fn search_documents(query_str: String, limit:

    usize) -> Result<Vec<SearchResult>, String> { // create reader let reader = search_index.index.reader().map_err(|e| e.to_string())?; // create query parser let query_parser = QueryParser::for_index( &search_index.index, vec![title, body, title_ngram, body_ngram], ); // parse query let query = query_parser .parse_query(&query_str) .map_err(|e| e.to_string())?; // create searcher let searcher = reader.searcher(); // search let top_docs = searcher .search(&query, &TopDocs::with_limit(limit)) .map_err(|e| e.to_string())?; } 検索する。 QueryParserを設定する。 title, body, title_ngram, body_ngram
  53. Flutter Lindera Tantivy 81 Flutter Code List<SearchResult> searchDocuments({ required String

    queryStr, required BigInt limit, }) => RustLib.instance.api.crateApiSearchSearchDocuments( queryStr: queryStr, limit: limit, ); flutter_rust_bridgeによって 自動生成されたコード。 flutter_rust_bridgeによって 自動生成されたFFI Function。
  54. Flutter Lindera Tantivy 82 Flutter Code class SearchPage extends ConsumerStatefulWidget

    { const SearchPage({super.key}); @override ConsumerState<SearchPage> createState() => _SearchPageState(); } class _SearchPageState extends ConsumerState<SearchPage> { Future<void> _performSearch() async { final results = searchDocuments( queryStr: _searchController.text.trim(), limit: BigInt.from(10), ); } } UIで使用するコールバックメ ソッド内で利用する。
  55. Flutter Lindera Tantivy 83 Flutter Code TextField( controller: _searchController, onSubmitted:

    (value) => _performSearch(), ), ElevatedButton.icon( onPressed: _isIndexInitialized && !_isLoading ? _performSearch : null, icon: const Icon(Icons.search), label: Text(l10n.search), ),
  56. Flutter Lindera Tantivy 84 Flutter プラグイン作成 flutter_rust_bridge_codegen create my_package --template

    plugin プロジェクト名の入力(設定) プラグインテンプレートから生成 ライブラリビルド 処理に関する設定 Rust Native Code (APIs)
  57. • オフライン状態でオンデバイス AI を実装するために、追加機能の開発が必要であった。 • オフラインで動作可能な検索エンジンの実装を追加で実施。 ◦ 知識ベース検索の性能向上とコンテキスト改善。 • CJK

    に対応可能なFlutter パッケージが存在しなかったため、独自に開発。 • Rust コードを Flutter で使用する方法を検討。 ◦ Rust Native + FFIの基本。 ◦ Tantivy を Flutterで使用する方法。(Rust Native + ffigen + FFI ) ◦ Lindera + Tantivyを Flutter アプリで使用する方法。(flutter_rust_bridge) • flutter_lindera_tantivy, flutter_tantivy 開発。 Summary 89
  58. • Flutter × Rust FFI の基本 ◦ https://github.com/JAICHANGPARK/flutter_rust_ffi_basic • Tantivy:

    Flutter × Rust: ffigen、FFI ◦ https://github.com/JAICHANGPARK/flutter_tantivy_native • Lindera-Tantivy: flutter_rust_bridge: ◦ https://github.com/JAICHANGPARK/flutter_lindera_tantivy • flutter_tantivy ◦ https://pub.dev/packages/flutter_tantivy • flutter_lindera_tantivy ◦ https://pub.dev/packages/flutter_lindera_tantivy Materials flutter_lindera_tantivy 90