fzyzcjy / flutter_rust_bridge

Flutter/Dart <-> Rust binding generator, feature-rich, but seamless and simple.
https://fzyzcjy.github.io/flutter_rust_bridge/
MIT License
3.61k stars 254 forks source link

Correct/Recommended usage of bindings #1908

Closed i5hi closed 2 weeks ago

i5hi commented 2 weeks ago

This isnt a feature request or a bug report.

I have a question about correct usage of the bindings produced by frb - specifically the usage of the init() method.

Pre 0.2.0 We would create bindings from api.rs which only contained methods, and have our types in a types.rs file.

After bindings, we would then manually write some code in dart in root.dart to create the dart classes and combine the methods in api.rs and types, to create the main classes for the library. We also had to write a loader file to load the binary into an ffi variable, on which we can call the api methods.


  static DynamicLibrary getDylib() {
    if (Platform.environment['FLUTTER_TEST'] == 'true') {
      try {
        return DynamicLibrary.open(_getUniTestDylibDir(Directory.current));
      } catch (e) {
        throw Exception("Unable to open the unit test dylib!");
      }
    }
    if (Platform.isIOS || Platform.isMacOS) {
      return DynamicLibrary.executable();
    } else if (Platform.isAndroid) {
      return DynamicLibrary.open("$name.so");
    } else if (Platform.isLinux) {
      return DynamicLibrary.open("$name.so");
    } else {
      throw Exception("not support platform:${Platform.operatingSystem}");
    }
  }
}

final ffi = LwkBridgeImpl(Dylib.getDylib());

Now with 0.2.0 we dont seem to need to create a root.dart because we can just write our structs with methods in rust and have that perfectly binded over to dart. The loader now just needs to be called once in main() and the binded classes will just work.

class LibLwk {
  static Future<void> init() async { // Call this only once in main
    try {
      if (!LwkCore.instance.initialized) {
        await LwkCore.init(externalLibrary: Dylib.getDylib());
      }
    } catch (e) {
      throw Exception("Failed to initialize lwk.");
    }
  }
}

I have noticed some libraries still using the root.dart pattern to encapsulate calling init() - but this seems unecessary because its extra work that has already been done by frb to capture the class semantics. There is the benefit that the end user of the library does not need to call init() but calling it once in main seems okay.

Is there anything wrong with this pattern?

For reference: https://github.com/SatoshiPortal/lwk-dart (Directly uses frb classes and expects user to call init() once in main)

Similar project: https://github.com/LtbLightning/bdk-flutter (Creates an encapsulated class in root.dart which calls init())

i5hi commented 2 weeks ago

Specifically, the encapsulation pattern:

https://github.com/LtbLightning/bdk-flutter/blob/main/lib/src/root.dart

The two libraries are very similar, both handle wallets and the Wallet object is wrapped in a Mutex because there is an internal call to a database that requires a lock to prevent race conditions.

fzyzcjy commented 2 weeks ago

Is there anything wrong with this pattern?

~~Hi, firstly, I guess there is nothing really wrong with it. flutter_rust_bridge does not restrict users to use any patterns. However, a main goal of flutter_rust_bridge is that, allow users to do as little extra work as possible. Therefore, I recommend to use flutter_rust_bridge to generate everything, and there is no need to manually write down an extra Dart wrapper. If your scenario requires wrapper code, feel free to create an issue and we can discuss whether it can be auto generated. Nevertheless, there are cases when it is hard to play with it without manual wrapper code, then it is pretty reasonable to wrap it manually.~~

(I am quite tired now, so maybe I do not get your question - feel free to tell me if so)

EDIT: Do you mean the https://github.com/LtbLightning/bdk-flutter/blob/3ecadf091327c180df2a3459d3faf1b671aa7bd8/lib/src/root.dart#L56 pattern? IMHO that may not be a super good idea. One way may be let users do initialization by themselves. Another way may be add a new feature in flutter_rust_bridge that automatically initializes it.

The reason why the initialization is designed to be done separately is that, there are synchronous functions, when there is no await possible, thus not possible to call initialization. In addition, if it is done automatically, it is not trivial for users to customize things like externalLibrary path. However, I guess this feature is reasonable to have.

i5hi commented 2 weeks ago

Thank you for taking the time to respond!

I have opted to let the user do the initialization themselves (in main) and create a wrapper only around the initializer which sets externalLibrary path and/or other customizations. So all the user has to do is Library.wrappedInit(). I just wanted to make sure this was not a bad pattern, which you have reassured me is not.

FRB 2.0 Has been a joy to work with btw. Cheers!

github-actions[bot] commented 19 hours ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new issue.