チュートリアル: Flutter と Rust の混合プログラミング、flutter_rust_bridge を使用して ffi コードを自動生成する

練習環境:Arch Linux

Flutter_rust_bridge 公式ドキュメント

Flutter環境設定チュートリアル| Rust環境設定チュートリアル

flutter_rust_bridge を使用するときに遭遇したいくつかの落とし穴を記録します。

Fluuter と Rust 環境がすでに構成されていると仮定します。

次に、flutter_rust_bridge テンプレートを使用して独自のプロジェクトを直接作成します。

走る:

git clone https://github.com/Desdaemon/flutter_rust_bridge_template && cd flutter_rust_bridge_template

それでは、プロジェクトを実行してみましょう。

flutter run

新しいコードを追加します。

ネイティブ/src/api.rsを編集します

コード ジェネレーターflutter_rust_bridge_codegenをインストールします。

cargo install flutter_rust_bridge_codegen

flutter_rust_bridge ドキュメントの例に従って、コード ジェネレーターを実行します。

flutter_rust_bridge_codegen --rust-input native/src/api.rs \
                            --dart-output lib/bridge_generated.dart

エラーが見つかりました:

stdbool.h ファイルが見つかりません

Bridge_generated.dart は次のエラー メッセージを報告します。

void store_dart_post_cobject(int ptr)
package:flutter_rust_bridge_template/bridge_generated.dart

Not to be used by normal users, but has to be public for generated code

Copied from FlutterRustBridgeWireBase.

'NativeWire.store_dart_post_cobject' ('void Function(int)') isn't a valid override of 'FlutterRustBridgeWireBase.store_dart_post_cobject' ('void Function(Pointer<NativeFunction<Bool Function(Int64, Pointer<Void>)>>)').dartinvalid_override
stub.dart(21, 8): The member being overridden.

走る:

flutter_rust_bridge_codegen --rust-input native/src/api.rs \
                            --dart-output lib/bridge_generated.dart \
                            --c-output ios/Runner/bridge_generated.h

ディスカバリーはまだ同じですか?何をすべきか?

解決:

CPATH環境変数を追加し、コード ジェネレーターを再実行します。

export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation" | rev | cut -d' ' -f1 | rev)/include"
flutter_rust_bridge_codegen --rust-input native/src/api.rs \
                            --dart-output lib/bridge_generated.dart \
                            --c-output ios/Runner/bridge_generated.h

今は普通です

lib/ffi.dartを開きます

致命的なエラーが 2 つありました

エラーメッセージ:

package:flutter_rust_bridge_template/bridge_generated.dart

The name 'Native' is defined in the libraries 'package:flutter_rust_bridge_template/bridge_definitions.dart' and 'package:flutter_rust_bridge_template/bridge_generated.dart'.
Try removing the export of one of the libraries, or explicitly hiding the name in one of the export directives.
[abstract class Native, abstract class Native]
The name 'Native' is defined in the libraries 'package:flutter_rust_bridge_template/bridge_definitions.dart' and 'package:flutter_rust_bridge_template/bridge_generated.dart'.
Try using 'as prefix' for one of the import directives, or hiding the name from all but one of the imports.

解決:

import 'bridge_settings.dart';を削除し、export 'bridge_settings.dart';

lib/ffi.dartの元のコード:

// This file initializes the dynamic library and connects it with the stub
// generated by flutter_rust_bridge_codegen.

import 'dart:ffi';

import 'bridge_generated.dart';
import 'bridge_definitions.dart';
export 'bridge_definitions.dart';

// Re-export the bridge so it is only necessary to import this file.
export 'bridge_generated.dart';
import 'dart:io' as io;

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));

lib/ffi.dartの変更されたコード:

// This file initializes the dynamic library and connects it with the stub
// generated by flutter_rust_bridge_codegen.

import 'dart:ffi';

import 'bridge_generated.dart';

// Re-export the bridge so it is only necessary to import this file.
export 'bridge_generated.dart';
import 'dart:io' as io;

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));

すべてが正常です:

main.dart调用rust函数

main.dart完整代码:

import 'package:flutter/material.dart';
import 'ffi.dart' if (dart.library.html) 'ffi_web.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // These futures belong to the state and are only initialized once,
  // in the initState method.
  late Future<Platform> platform;
  late Future<bool> isRelease;

  late Future<String> test;

  @override
  void initState() {
    super.initState();
    platform = api.platform();
    isRelease = api.rustReleaseMode();

    test = api.test();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text("You're running on"),
            FutureBuilder<List<dynamic>>(
                future: Future.wait([test]),
                builder: (context, snap) {
                  final data = snap.data;
                  if (data == null) {
                    return const Text("Loading");
                  }

                  return Text('${data[0]}');
                }),

            // To render the results of a Future, a FutureBuilder is used which
            // turns a Future into an AsyncSnapshot, which can be used to
            // extract the error state, the loading state and the data if
            // available.
            //
            // Here, the generic type that the FutureBuilder manages is
            // explicitly named, because if omitted the snapshot will have the
            // type of AsyncSnapshot<Object?>.
            FutureBuilder<List<dynamic>>(
              // We await two unrelated futures here, so the type has to be
              // List<dynamic>.
              future: Future.wait([platform, isRelease]),
              builder: (context, snap) {
                final style = Theme.of(context).textTheme.headline4;
                if (snap.error != null) {
                  // An error has been encountered, so give an appropriate response and
                  // pass the error details to an unobstructive tooltip.
                  debugPrint(snap.error.toString());
                  return Tooltip(
                    message: snap.error.toString(),
                    child: Text('Unknown OS', style: style),
                  );
                }

                // Guard return here, the data is not ready yet.
                final data = snap.data;
                if (data == null) return const CircularProgressIndicator();

                // Finally, retrieve the data expected in the same order provided
                // to the FutureBuilder.future.
                final Platform platform = data[0];
                final release = data[1] ? 'Release' : 'Debug';
                final text = const {
                      Platform.Android: 'Android',
                      Platform.Ios: 'iOS',
                      Platform.MacApple: 'MacOS with Apple Silicon',
                      Platform.MacIntel: 'MacOS',
                      Platform.Windows: 'Windows',
                      Platform.Unix: 'Unix',
                      Platform.Wasm: 'the Web',
                    }[platform] ??
                    'Unknown OS';
                return Text('$text ($release)', style: style);
              },
            )
          ],
        ),
      ),
    );
  }
}

运行项目:

flutter run

おすすめ

転載: blog.csdn.net/love906897406/article/details/128702597