Skip to main content
Version: 0.3

Flutter Setup

This tutorial will guide you through integrating the iOS bindings and Android bindings into an Flutter) project. Before you begin, make sure you’ve completed the "Getting Started - 3. Mopro build" process with selecting iOS platform and Android platform and have the MoproiOSBindings and MoproAndroidBindings folder ready:

Flutter is a framework for building natively compiled, multi-platform applications from a single codebase.

In this tutorial, you will learn how to create a native Mopro module on both Android and iOS simulators/devices.

Android app screenshotiOS app screenshot
info

In this example, we use Circom circuits and their corresponding .zkey files. The process is similar for other provers.

0. Prerequisites

  1. Install Flutter

    If Flutter is not already installed, you can follow the official Flutter installation guide for your operating system.

  2. Check Flutter Environment

    After installing Flutter, verify that your development environment is properly set up by running the following command in your terminal:

    flutter doctor

    This command will identify any missing dependencies or required configurations.

  3. Install Flutter Dependencies

    Navigate to the root directory of the project in your terminal and run:

    flutter pub get

    This will install the necessary dependencies for the project.

  4. Create a Flutter App

    If you already have Flutter app, you can skip this step. If you don't have a one, follow this tutorial to set one up. Or run

    flutter create <YOUR_FLUTTER_APP>
  5. Version Requirements

    Ensure you meet the following minimum version requirements:

    • Flutter SDK: 3.0.0 or higher
    • Android:
      • minSdk: 24
      • compileSdk: 34
      • targetSdk: 34
    • JNA: 5.13.0 (required for Android bindings)

    You can check your Flutter version by running:

    flutter --version

1. Copy React Native Module

  • Copy the exported mopro_flutter_bindings folder into the project directory you created with the CLI. After doing so, your project structure should look like this:

    .
    ├── android
    ├── assets
    ├── ios
    ├── lib
    ├── mopro_flutter_bindings
    ├── pubspec.yaml
    ...

2. Use the plugin

info

Please refer to flutter-app to see the latest update.

Follow the steps below to integrate mopro plugin.

2-1. Add the plugin to pubspec.yaml as a dependency:

---
dependencies:
flutter:
sdk: flutter
mopro_flutter_plugin:
path: ./mopro_flutter_plugin

2-2. Copy keys in the assets folder like this


flutter-app/
├── ...
├── assets/multiplier2_final.zkey
└── lib/main.dart

and update pubspec.yaml

flutter:
assets:
- assets/multiplier2_final.zkey

2-3. Initialize the RustLib

In your main function in e.g. lib/main.dart, initialize the RustLib before running the app.

lib/main.dart
import 'package:mopro_flutter_bindings/src/rust/frb_generated.dart';

Future<void> main() async {
await RustLib.init();
runApp(const MyApp());
}

2-4. Generate proofs in the app

import 'package:mopro_flutter_bindings/src/rust/third_party/mopro_example_app.dart'; // Change to the name of your package

var inputs = '{"a":["3"],"b":["5"]}';
var proofResult = await generateCircomProof(
zkeyPath: zkeyPath,
circuitInputs: inputs,
proofLib: ProofLib.arkworks);
var valid = await verifyCircomProof(
zkeyPath: zkeyPath,
proofResult: proofResult,
proofLib: ProofLib.arkworks);
info

To get zkey path from assets, you can refer to the script: copyAssetToFileSystem()

3. Customizing the zKey

3-1. Configurate zkey in pubspec.yaml

Place your .zkey file in your app's assets folder and remove the example file assets/multiplier2_final.zkey. If your .zkey has a different file name, don't forget to update the asset definition in your app's pubspec.yaml:

assets:
- - assets/multiplier2_final.zkey
+ - assets/your_new_zkey_file.zkey

3-2. Load the zkey

Load the new .zkey file in your Dart code by updating the file path in lib/main.dart:

var inputs = '{"a":["3"],"b":["5"]}';
- final zkeyPath = await copyAssetToFileSystem("assets/multiplier2_final.zkey");
+ final zkeyPath = await copyAssetToFileSystem("assets/your_new_zkey_file.zkey");

Don't forget to modify the input values for your specific case!

7. What's next

  • Update your ZK circuits as needed. After making changes, be sure to run:

    mopro build
    info

    If you created your app using the Mopro CLI, you don’t need to run mopro update. The app will automatically use the mopro_flutter_bindings from the relative path.

    Copy the mopro_flutter_bindings folder into your Flutter app directory, or run the following command:

    cp -r ../path/to/your/bindings/mopro_flutter_bindings ./your_flutter_app

    This ensures the bindings are regenerated and reflect your latest updates.

    warning

    The bindings only work locally due to the current Cargo.toml configuration in mopro_flutter_bindings/rust/Cargo.toml. You can update the dependency source to point to a GitHub repository or a published crate on crates.io, if you want to make it available remotely. If your local source functions differ from the versions on GitHub or crates.io, run

    mopro build

    again in your local directory, copy the generated bindings into your Flutter project, and update the source path in mopro_flutter_bindings/rust/Cargo.toml.

  • Build your mobile app frontend according to your business logic and user flow.

  • Expose additional Rust functionality: If a function is missing in Swift, Kotlin, React Native, or Flutter, you can:

    • Add the required Rust crate in Cargo.toml
    • Make the function pub in src/lib.rs to expose it (See the Rust setup guide for details).
      Once exported, the function will be available across all supported platforms.

⚠️ Avoid using the *const ::std::ffi::c_void type in lib.rs

When running Flutter Rust Bridge, this type can cause build errors like:

Running Xcode build...
Xcode build done. 3.1s
Failed to build iOS app
Error (Xcode): failed to run custom build command for
`mopro-example-app v0.1.0
(/Users/...)`
/Users/.../flutter/ios/Pods/SEVERE:0:0

To identify problematic functions, run:

cargo expand --lib

and check for any functions that use *const ::std::ffi::c_void as input or output types.

For example, when using rust_witness::witness!(...), it may expose public functions that include the *const ::std::ffi::c_void type.

To avoid this, wrap the macro inside a private module and expose your own safe public functions instead:

src/lib.rs
mod witness {
rust_witness::witness!(...);
}

// Expose a safe public function instead of directly using raw pointers
pub fn generate_circom_proof(
zkey_path: String,
circuit_inputs: String,
proof_lib: ProofLib,
) -> Result<CircomProofResult, MoproError> {
// ...
}

This approach keeps the FFI layer internal and prevents *const ::std::ffi::c_void types from appearing in your public API.

⚠️ Error when running flutter run --release

If you see an error like this:

E/AndroidRuntime(17363): java.lang.UnsatisfiedLinkError: Can't obtain peer field ID for class com.sun.jna.Pointer
...

This happens because UniFFI relies on JNA, which is not compatible with code shrinking or obfuscation enabled in Android release builds.

  • See: Android code shrinking
  • See: UniFFI Kotlin JNA requirements

✅ Solution

To fix this, disable code shrinking in your Android build.gradle for release builds:

android {
buildTypes {
release {
minifyEnabled false // Disable code shrinking & obfuscation
shrinkResources false // Optional: also disable resource shrinking
}
}
}

After applying this change, you should be able to run:

flutter run --release

without hitting the JNA-related crash.