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. 


In this example, we use Circom circuits and their corresponding .zkey files. The process is similar for other provers.
0. Prerequisites
- 
Install Flutter
If Flutter is not already installed, you can follow the official Flutter installation guide for your operating system.
 - 
Check Flutter Environment
After installing Flutter, verify that your development environment is properly set up by running the following command in your terminal:
flutter doctorThis command will identify any missing dependencies or required configurations.
 - 
Install Flutter Dependencies
Navigate to the root directory of the project in your terminal and run:
flutter pub getThis will install the necessary dependencies for the project.
 - 
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> 
1. Creating a Native Module
Create a plugin to integrate Mopro bindings into your project.
flutter create mopro_flutter_plugin -t plugins
To learn more about flutter packages/plugins, please refer to Flutter - Developing packages & plugins
To support both iOS and Android, we need to build native modules for each platform.
Start by adding the necessary platforms to the plugin:
Navigate to the mopro_flutter_plugin directory
cd mopro_flutter_plugin
and run the following command:
flutter create -t plugin --platforms ios,android .
2. Implement the module on iOS
Please refer to flutter-app to see the latest update.
2-1 Use a framework
- 
Get the
MoproiOSBindingsfrommopro build.infoSee Getting Started
 - 
Place the
MoproiOSBindings/mopro.swiftfile tomopro_flutter_plugin/ios/Classes/mopro.swift
Place theMoproiOSBindings/MoproBindings.xcframeworkfile tomopro_flutter_plugin/ios/MoproBindings.xcframework.
The structure will look likeflutter-app/
├── ...
└── mopro_flutter_plugin
└── ios/
├── ...
├── Classes/
│ ├── ...
│ └── mopro.swift
└── MoproBindings.xcframework/... - 
Bundle the bindings in
mopro_flutter_plugin/ios/mopro_flutter_plugin.podspec/mopro_flutter_plugin/ios/mopro_flutter_plugin.podspec...
s.source_files = 'Classes/**/*'
s.vendored_frameworks = 'MoproBindings.xcframework'
s.preserve_paths = 'MoproBindings.xcframework/**/*'
... 
2-2 Create convertible types for Javascript library with swift.
- 
Create these types in the file:
mopro_flutter_plugin/ios/Classes/MoproFlutterPlugin.swift/mopro_flutter_plugin/ios/Classes/MoproFlutterPlugin.swiftclass FlutterG1 {
let x: String
let y: String
let z: String
init(x: String, y: String, z: String) {
self.x = x
self.y = y
self.z = z
}
}
// ...noteSee the full implementation here:
MoproFlutterPlugin.swift - 
Define helper functions to bridge types between the Mopro bindings and the Flutter framework:
/mopro_flutter_plugin/ios/Classes/MoproFlutterPlugin.swift// convert the mopro proofs to be exposed to Flutter framework
func convertCircomProof(res: CircomProofResult) -> [String: Any] {
let g1a = FlutterG1(x: res.proof.a.x, y: res.proof.a.y, z: res.proof.a.z)
// ...
}
// convert the Flutter proofs to be used in mopro bindings
func convertCircomProofResult(proof: [String: Any]) -> CircomProofResult {
let proofMap = proof["proof"] as! [String: Any]
let aMap = proofMap["a"] as! [String: String]
let g1a = G1(x: aMap["x"] ?? "0", y: aMap["y"] ?? "0", z: aMap["z"] ?? "1")
// ...
}noteSee the full implementation here:
MoproFlutterPlugin.swift - 
Define the native module API. See the Writing custom platform-specific code for details.
 
public class MoproFlutterPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "mopro_flutter_plugin", binaryMessenger: registrar.messenger())
    let instance = MoproFlutterPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }
  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
      switch call.method {
         case "generateCircomProof":
         guard let args = call.arguments as? [String: Any],
            let zkeyPath = args["zkeyPath"] as? String,
            let inputs = args["inputs"] as? String,
            let proofLib = args["proofLib"] as? ProofLib
         else {
            result(FlutterError(code: "ARGUMENT_ERROR", message: "Missing arguments", details: nil))
            return
         }
         do {
            // Call the function from mopro.swift
            let proofResult = try generateCircomProof(
               zkeyPath: zkeyPath, circuitInputs: inputs, proofLib: proofLib)
            let resultMap = convertCircomProof(res: proofResult)
            // Return the proof and inputs as a map supported by the StandardMethodCodec
            result(resultMap)
         } catch {
            result(
               FlutterError(
                  code: "PROOF_GENERATION_ERROR", message: "Failed to generate proof",
                  details: error.localizedDescription))
         }
      }
   }
   // ...
}
See the full implementation here: MoproFlutterPlugin.swift
3. Implement the module on Android
3-1 Add dependency for jna in the file build.gradle.
dependencies {
  implementation("net.java.dev.jna:jna:5.13.0@aar")
}
3-2 Include Mopro bindings in the native Android module
- 
Get the
MoproAndroidBindingsfrommopro build.infoSee Getting Started
 - 
Move the
jniLibsdirectory tomopro_flutter_plugin/android/src/main.
And moveuniffidirectory tomopro_flutter_plugin/android/src/main/kotlin.
The folder structure should be as follows:flutter-app/
├── ...
└── mopro_flutter_plugin
└── android/
├── ...
└── src/
├── ...
└── main/
├── ...
├── jniLibs/...
└── kotlin/
├── ...
└── uniffi/mopro/mopro.kt 
3-3 Create convertible types for Javascript library with kotlin.
- 
Create these types in the file:
mopro_flutter_plugin/android/src/main/kotlin/com/example/mopro_flutter_plugin/MoproFlutterPlugin.kt/mopro_flutter_plugin/android/src/main/kotlin/com/example/mopro_flutter_plugin/MoproFlutterPlugin.ktclass FlutterG1(x: String, y: String, z: String) {
val x = x
val y = y
val z = z
}
// ...noteSee the full implementation here:
MoproFlutterPlugin.kt - 
Define helper functions to bridge types between the Mopro bindings and the Flutter framework:
/mopro_flutter_plugin/android/src/main/kotlin/com/example/mopro_flutter_plugin/MoproFlutterPlugin.kt// convert the mopro proofs to be exposed to Flutter framework
fun convertCircomProof(res: CircomProofResult): Map<String, Any> {
val g1a = FlutterG1(res.proof.a.x, res.proof.a.y, res.proof.a.z)
// ...
}
// convert the Flutter proofs to be used in mopro bindings
fun convertCircomProofResult(proofResult: Map<String, Any>): CircomProofResult {
val proofMap = proofResult["proof"] as Map<String, Any>
val aMap = proofMap["a"] as Map<String, Any>
val g1a = G1(
aMap["x"] as String,
aMap["y"] as String,
aMap["z"] as String
)
// ...
}noteSee the full implementation here:
MoproFlutterPlugin.kt - 
Define the native module API. See the Writing custom platform-specific code for details.
 
class MoproFlutterPlugin: FlutterPlugin, MethodCallHandler {
  /// The MethodChannel that will the communication between Flutter and native Android
  ///
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
  /// when the Flutter Engine is detached from the Activity
  private lateinit var channel : MethodChannel
  override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    channel = MethodChannel(flutterPluginBinding.binaryMessenger, "mopro_flutter_plugin")
    channel.setMethodCallHandler(this)
  }
  override fun onMethodCall(call: MethodCall, result: Result) {
      if (call.method == "generateCircomProof") {
         val zkeyPath = call.argument<String>("zkeyPath") ?: return result.error(
            "ARGUMENT_ERROR",
            "Missing zkeyPath",
            null
         )
         val inputs =
            call.argument<String>("inputs") ?: return result.error(
               "ARGUMENT_ERROR",
               "Missing inputs",
               null
            )
         val proofLibIndex = call.argument<Int>("proofLib") ?: return result.error(
            "ARGUMENT_ERROR",
            "Missing proofLib",
            null
         )
         val proofLib = if (proofLibIndex == 0) ProofLib.ARKWORKS else ProofLib.RAPIDSNARK
         val res = generateCircomProof(zkeyPath, inputs, proofLib)
         val resultMap = convertCircomProof(res)
         result.success(resultMap)
      // ...
      } else {
         result.notImplemented()
      }
  }
   // ...
}
See the full implementation here: MoproFlutterPlugin.kt
4. Define Dart APIs
Please refer to flutter-app to see the latest update.
- 
Define the types for the native module. Add the following types in the file
mopro_flutter_plugin/lib/mopro_types.dart:/mopro_flutter_plugin/lib/mopro_types.dartimport 'dart:typed_data';
class G1Point {
final String x;
final String y;
final String z;
G1Point(this.x, this.y, this.z);
String toString() {
return "G1Point(\nx: $x, \ny: $y, \nz: $z)";
}
}
enum ProofLib { arkworks, rapidsnark }
// ...noteSee the full implementation here:
mopro_types.dart - 
Add the native module's API functions in these files.
 
class MethodChannelMoproFlutterPlugin extends MoproFlutterPluginPlatform {
   /// The method channel used to interact with the native platform.
   
   final methodChannel = const MethodChannel('mopro_flutter_plugin');
   
   Future<CircomProofResult?> generateCircomProof(
      String zkeyPath, String inputs, ProofLib proofLib) async {
      final proofResult = await methodChannel
        .invokeMethod<Map<Object?, Object?>>('generateCircomProof', {
            'zkeyPath': zkeyPath,
            'inputs': inputs,
            'proofLib': proofLib.index,
         });
      if (proofResult == null) {
         return null;
      }
      var circomProofResult = CircomProofResult.fromMap(proofResult);
      return circomProofResult;
   }
   // ...
}
See the full implementation here: mopro_flutter_method_channel.dart
abstract class MoproFlutterPluginPlatform extends PlatformInterface {
   //...
   Future<CircomProofResult?> generateCircomProof(
      String zkeyPath, String inputs, ProofLib proofLib) {
         throw UnimplementedError('generateCircomProof() has not been implemented.');
   }
   //...
}
See the full implementation here: mopro_flutter_platform_interface.dart
class MoproFlutterPlugin {
   Future<String> copyAssetToFileSystem(String assetPath) async {
      // Load the asset as bytes
      final byteData = await rootBundle.load(assetPath);
      // Get the app's document directory (or other accessible directory)
      final directory = await getApplicationDocumentsDirectory();
      //Strip off the initial dirs from the filename
      assetPath = assetPath.split('/').last;
      final file = File('${directory.path}/$assetPath');
      // Write the bytes to a file in the file system
      await file.writeAsBytes(byteData.buffer.asUint8List());
      return file.path; // Return the file path
   }
   Future<CircomProofResult?> generateCircomProof(
      String zkeyFile, String inputs, ProofLib proofLib) async {
      return await copyAssetToFileSystem(zkeyFile).then((path) async {
         return await MoproFlutterPlatform.instance
            .generateCircomProof(path, inputs, proofLib);
      });
   }
   //...
}
See the full implementation here: mopro_flutter.dart
5. Use the plugin
Follow the steps below to integrate mopro plugin.
- 
Add the plugin to
pubspec.yamlas a dependency:---
dependencies:
flutter:
sdk: flutter
mopro_flutter_plugin:
path: ./mopro_flutter_plugin - 
Copy keys in the
assetsfolder like this
flutter-app/
├── ...
├── assets/multiplier2_final.zkey
└── lib/main.dartand update
pubspec.yamlflutter:
assets:
- assets/multiplier2_final.zkey - 
Generate proofs in the app
import 'package:mopro_flutter_plugin/mopro_flutter_plugin.dart';
import 'package:mopro_flutter_plugin/mopro_types.dart';
final _moproFlutterPlugin = MoproFlutterPlugin();
var inputs = '{"a":["3"],"b":["5"]}';
var proofResult = await _moproFlutterPlugin.generateCircomProof(
"assets/multiplier2_final.zkey",
inputs,
ProofLib.arkworks
); 
6. Customizing the zKey
- 
Place your
.zkeyfile in your app's assets folder and remove the example fileassets/multiplier2_final.zkey. If your.zkeyhas a different file name, don't forget to update the asset definition in your app'spubspec.yaml:assets:
- - assets/multiplier2_final.zkey
+ - assets/your_new_zkey_file.zkey - 
Load the new
.zkeyfile in your Dart code by updating the file path inlib/main.dart:var inputs = '{"a":["3"],"b":["5"]}';
- proofResult = await _moproFlutterPlugin.generateCircomProof("assets/multiplier2_final.zkey", inputs, ProofLib.arkworks);
+ proofResult = await _moproFlutterPlugin.generateCircomProof("assets/your_new_zkey_file.zkey", inputs, ProofLib.arkworks); 
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:
This ensures the bindings are regenerated and reflect your latest updates.
mopro build
mopro update --dest ../MyFlutterApp - 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 - Annotate your function with 
#[uniffi::export](See the Rust setup guide for details).
Once exported, the function will be available across all supported platforms. 
 - Add the required Rust crate in 
 
⚠️ 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.